<!--quoteo(post=1896926:date=Jan 23 2012, 08:50 PM:name=Yuuki)--><div class='quotetop'>QUOTE (Yuuki @ Jan 23 2012, 08:50 PM) <a href="index.php?act=findpost&pid=1896926"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Here the wall walking problem :

Given :

- The three angles of the camera: <b>(pitch, yaw, roll)</b> - The wall normal: <b>n</b> - Two mouse input angles: <b>input.yaw</b> and <b>input.pitch</b>

Write an expression for <b>(pitch, yaw, roll)=f(n,input.y,input.p)</b>.

Note that n = n(t) and that df / dn should be smartly chosen (i.e. you don't want changes in the wall normal to make crazy changes in the camera angles).<!--QuoteEnd--></div><!--QuoteEEnd-->

Don't forget the movement inputs too, input.x. Oh, and I assume you mean input.y as input.yaw, but input.y is something else...

<!--quoteo(post=1896960:date=Jan 23 2012, 11:48 PM:name=Yuuki)--><div class='quotetop'>QUOTE (Yuuki @ Jan 23 2012, 11:48 PM) <a href="index.php?act=findpost&pid=1896960"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->input.y is input.yaw and input.p is input.pitch

Don't need to worry about the movement, it's easy once the camera is set (because you can use the camera axis).<!--QuoteEnd--></div><!--QuoteEEnd-->

Ah, nice. I meant before when we tried to transform the coordinates we transformed input.x into input.y and that was the normal up-down direction, or strafing on a wall with view rotation. Would be bad to not get it working because of confusing one with the other :)

I didn't know you could relate it to the camera axis, that sounds quite easy to do. With that, can't you do a quick fix for wallwalking changeing wallwalking tilt to 1 and then relate the inputs to the camera? That should make it working smoothly for walls atleast then. Probably need to change tilt speed too.

With camera(pitch,yaw,roll) and vector[x,y,z], we have: <!--coloro:#AAAAFF--><span style="color:#AAAAFF"><!--/coloro-->- camera.absolute = (0,0,0) // this would be your default camera with up as "absolute y", right as "absolute x", forward as "absolute z" - camera.reference = f(U, n, camera.absolute) - camera.current = f(input.pitch, input.yaw, camera.reference) - U{absolute} = [0,1,0] // "up" - n{relative} = f("surface") = [x,y,z] // the surface normal, a unit vector<!--colorc--></span><!--/colorc--> <!--coloro:#AAFFAA--><span style="color:#AAFFAA"><!--/coloro-->> n' = normalise[0,y,z] // x is pitch axis > n* = normalise[x,y,0] // z is roll axis > camera.reference(pitch) = camera.absolute(pitch) + arccos(U.n') // dot product for angle between normal and up; unsure if this will yield positive or negative correctly > camera.reference(yaw) = camera.absolute(yaw) > camera.reference(roll) = camera.absolute(roll) + arccos(U.n*) // dot product for angle between normal and up; unsure if this will yield positive or negative correctly<!--colorc--></span><!--/colorc--> <!--coloro:green--><span style="color:green"><!--/coloro-->>/ camera.current(pitch) = camera.reference(pitch) + f(input.pitch,input.yaw) // ??? >/ camera.current(yaw) = camera.reference(yaw) + f(input.pitch,input.yaw) // ??? >/ camera.current(roll) = camera.reference(roll) + f(input.pitch,input.yaw) // ???<!--colorc--></span><!--/colorc-->

The next step is to apply input.pitch and input.yaw (which are relative) to camera(pitch,yaw,roll) (which is absolute). I'm hoping there is a built-in function that does this, otherwise it could be problematic. Because, for example, if a) camera.reference(roll) = 0deg (clockwise), +input.pitch (up, relative) => up (absolute, +pitch), +input.yaw (right, relative) => right (absolute, +yaw) <!--coloro:gray--><span style="color:gray"><!--/coloro--><!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->// +pitch = +1*input.pitch +0*input.yaw // +yaw = +1*input.yaw +0*input.pitch<!--sizec--></span><!--/sizec--><!--colorc--></span><!--/colorc--> b) camera.reference(roll) = 90deg (clockwise), +input.pitch (up, relative) => left (absolute, -yaw), +input.yaw (right, relative) => down (absolute, -pitch) <!--coloro:gray--><span style="color:gray"><!--/coloro--><!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->// +pitch = +0*input.pitch -1*input.yaw // +yaw = +0*input.yaw -1*input.pitch<!--sizec--></span><!--/sizec--><!--colorc--></span><!--/colorc--> c) camera.reference(roll) = 180deg (clockwise), +input.pitch (up, relative) => down (absolute, -pitch), +input.yaw (right, relative) => left (absolute, -yaw) <!--coloro:gray--><span style="color:gray"><!--/coloro--><!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->// +pitch = -1*input.pitch +0*input.yaw // +yaw = -1*input.yaw +0*input.pitch<!--sizec--></span><!--/sizec--><!--colorc--></span><!--/colorc--> camera.reference(roll) = 270deg (clockwise), +input.pitch (up, relative) => right (absolute, +yaw), +input.yaw (right, relative) => up (absolute, +pitch) <!--coloro:gray--><span style="color:gray"><!--/coloro--><!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->// +pitch = +0*input.pitch +1*input.yaw // +yaw = +0*input.yaw +1*input.pitch<!--sizec--></span><!--/sizec--><!--colorc--></span><!--/colorc-->

So, assuming all my guesstimation is correct: <!--coloro:#AAFFAA--><span style="color:#AAFFAA"><!--/coloro-->>/ camera.current(pitch) = camera.reference(pitch) + cos(camera.reference(roll))*input.pitch - sin(camera.reference(roll))*input.yaw >/ camera.current(yaw) = camera.reference(yaw) + cos(camera.reference(roll))*input.yaw - sin(camera.reference(roll))*input.pitch >/ camera.current(roll) = camera.reference(roll)<!--colorc--></span><!--/colorc-->

But then it doesn't consider when camera.reference(yaw) and camera.reference(pitch) are not 0, which would lead to a similar sort of thing, with a similar sort of solution. I'm too lazy to work it out right now.

I'm a bit confused by the notation, by camera.reference(pitch) do you mean camera.reference.pitch, i.e. the pitch of the reference camera, or that camera.reference if a function of pitch ?

What we want is to express pitch in function of n and input, like pitch = f(n,input), if I rewrite what you found in theses terms I get :

1) Imagine you face a vertical wall, you don't touch your mouse and walk straight to the wall, shouldn't the camera rotate of 90 degrees around the xAxis ? You would get your yAxis aligned with the wall normal and your zAxis pointing the ceiling.

2) Imagine you are perpendicular to vertical wall, you don't touch your mouse and strafe toward the wall, shouldn't the camera rotate of 90 degrees around the zAxis this time ?

So the function should also take into account the current orientation :

new pitch = f(n,input.yaw,input.pitch, current.yaw, current.pitch, current.roll)

<!--quoteo(post=1897075:date=Jan 24 2012, 12:18 PM:name=Yuuki)--><div class='quotetop'>QUOTE (Yuuki @ Jan 24 2012, 12:18 PM) <a href="index.php?act=findpost&pid=1897075"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->1) Imagine you face a vertical wall, you don't touch your mouse and walk straight to the wall, shouldn't the camera rotate of 90 degrees around the xAxis ? You would get your yAxis aligned with the wall normal and your zAxis pointing the ceiling.

2) Imagine you are perpendicular to vertical wall, you don't touch your mouse and strafe toward the wall, shouldn't the camera rotate of 90 degrees around the zAxis this time ?<!--QuoteEnd--></div><!--QuoteEEnd-->

Well, we don't want 1). If you aim straight to the wall then you should keep aiming straight to the wall, and be about to walk up on the wall but aiming "down" into the wall. So if you pitch up you should aim up towards the ceiling, if you yaw then your view should just rotate while you keep staring "down" at the wall. If it doesn't behave like this (like in BallMan and my experience from AvP2) then you can't keep your aim at a certain point as you transition between different surfaces.

Uh, I didn't really use any official notation, since I'm not sure what they would be. But, essentially: Camera.X is an array that has the parameters pitch, yaw and roll, like (pitch,yaw,roll); so Camera.X(pitch) would just be the value of pitch. Vector is an array that has the parameters x, y and z, like [x,y,z]; so Vector[x] would just be the value of x. I guess I was inconsistent with this though.

There are three different camera arrays: camera.absolute (your starting point camera based on a flat horizontal plane; that is, the normal is parallel with the absolute y-axis) camera.reference (your camera offset, due to the wall angle(s), with reference to the absolute axis) camera.current (your camera based on your inputs)

I don't actually know the in-game implementation, so I've been approaching it from a logical perspective, using intermediates. camera.absolute = (0,0,0) => pitch = 0, yaw = 0, roll = 0 As I explained in less detail earlier, it corresponds to when: - subjective "right" = absolute "x-axis"; the x-axis is the pitch axis - subjective "up" = absolute "y-axis"; the y-axis is the yaw axis - subjective "forward" = absolute "; the z-axis is the roll axis camera.reference depends on the wall angle(s) (specifically, the wall-normal). Your subjective "right", "up" and "forward" change with the surface.

I'm not even sure if the game uses states or deltas. All my work assumes states, but you can simply take the difference for deltas.

Also, arccos(U.n') != arccos(U.n*) n' is a component vector (sort of) of the wall normal in the z-(forward)direction (that is, setting x to zero); therefore the angle between it and absolute "up" gives "pitch". n* is a component vector (sort of) of the wall normal in the x-(right)direction (that is, setting z to zero); therefore the angle between it and absolute "up" gives roll.

<i><!--coloro:grey--><span style="color:grey"><!--/coloro-->Both situations you described, 1 and 2, should work with my implementation (assuming it can be implemented). 0) You are on the ground, your normal starts at [0,1,0], your camera is (0,0,0). 1) When walking ahead onto to a wall in front of you, your normal changes to [0,0,-1], your camera is (90,0,0). Pitched up, or pitched backward, 90 degrees. 2) When strafing onto a wall to your right, your normal changes to [-1,0,0], your camera is (0,0,-90) or (0,0,270). Rolled to the left, 90 degrees. 3) When strafing onto a wall to your left, your normal changes to [1,0,0], your camera is (0,0,90). Rolled to the right, 90 degrees. 4) From 1), when walking onto the ceiling, your normal becomes [0,-1,0], your camera is (180,0,180). Upside down. Pitched up, or pitched backward, 180 degrees; and rolled to the right 180 degrees. 5) From 2) or 3), when walking onto the ceiling, your normal becomes [0,-1,0], your camera is (180,0,180). Upside down. Pitched up, or pitched backward, 180 degrees; and rolled to the right 180 degrees. Note that 4 and 5 describe the same situation (upside down), and it changes both pitch and roll parameters. This is because yaw is independent of the surface normal.<!--colorc--></span><!--/colorc--></i> This needs to be checked. I'm actually not 100% sure about whether or not yaw goes into it. I suppose you could take the (which?) perpendicular of the surface normal and compare that against [1,0,0], taking the difference in angle as the reference yaw.

Also note, the last section is incomplete. I've only, so far, considered when roll is kept constant, I haven't considered when pitch and yaw are constant. My hope was that the game wouldn't need it. Essentially it should be able to add a subjective pitch,yaw,roll (your input) to the reference pitch, yaw, roll (your camera based on the surface). Kind of like how your position in the game world is based on an absolute reference, but you yourself have personal x,y,z-axes.

<!--quoteo(post=1897075:date=Jan 24 2012, 07:18 PM:name=Yuuki)--><div class='quotetop'>QUOTE (Yuuki @ Jan 24 2012, 07:18 PM) <a href="index.php?act=findpost&pid=1897075"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->new pitch = f(n,input.yaw,input.pitch, current.yaw, current.pitch, current.roll)<!--QuoteEnd--></div><!--QuoteEEnd--> Seems like the game wants deltas. In which case you need to change the parameters to: new pitch = f(current.n,new.n,input.yaw,input.pitch, current.yaw, current.pitch, current.roll) new yaw = f(current.n,new.n,input.yaw,input.pitch, current.yaw, current.pitch, current.roll) new roll = f(current.n,new.n,input.yaw,input.pitch, current.yaw, current.pitch, current.roll) Basically you need to: 1) consider the (which is what I've done with camera.reference, though it's probably buggy).

I'm assuming that pitch/yaw/roll are based on an absolute reference: when <!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec-->- subjective "right" = absolute "x-axis"; the x-axis is the pitch axis - subjective "up" = absolute "y-axis"; the y-axis is the yaw axis - subjective "forward" = absolute "; the z-axis is the roll axis<!--QuoteEnd--></div><!--QuoteEEnd-->

Also, I want to know, can the game add: <i>Absolute (Pitch, Yaw, Roll) + Transformed(Subjective (Pitch, Yaw, Roll))</i> the same way that the game can add <i>Absolute (x, y, z) + Transformed(Subjective (x, y, z))</i> For example, if you're at [1,0,0] (right of the origin), you're facing in the direction [0,1,0] (the "up" direction), and you <b>subjectively</b> move [0,0,2] (forward, in the "up" direction), you <b>absolutely</b> move [0,2,0] ("in the up direction"), and end up at [1,2,0]. The game appears to do this on its own. Can you do the same thing with pitch, yaw, roll?

Can you point out the code that governs this sort of stuff for the ground-based implementation? It would give us a much better idea.

If I'm correct, from the code perspective things are very simple, the camera orientation is defined by three angles (measured in the game reference frame). Then theses angles are set in Player:UpdateViewAngles(input) :

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> // Update to the current view angles. local viewAngles = Angles(input.pitch, input.yaw, 0) self:SetViewAngles(viewAngles)<!--c2--></div><!--ec2-->

Yes but you do the dot product with U=[0,1,0], no ?

[0,1,0] . [0,y,z] = y [0,1,0] . [x,y,0] = y

A last note about programing, we can easily transform angles into coordinates (the three axis representation) and coordinates into angles. Maybe it's easier to think in axis.

Soul_RiderMod BeanJoin Date: 2004-06-19Member: 29388Members, Constellation, Squad Five Blue

edited January 2012

Downloaded the source code for AvP to see how they enabled wall-walking.

Breaking down the file Pmove.c, I have the code used for setting the vector direction of the player, so it can jump in the direction it is looking at:

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> if (notTooSteep) { /* alien can jump in the direction it's looking */ if (AvP.PlayerType == I_Alien) { VECTORCH viewDir;

Then there is the follow-up code to translate the local player change to the world...

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> /* rotate LinVelocity into world space */ RotateVector(&dynPtr->LinVelocity,&dynPtr->OrientMat); }

playerStatusPtr->ForwardInertia = forwardSpeed; playerStatusPtr->StrafeInertia = strafeSpeed; playerStatusPtr->TurnInertia = turnSpeed; } /*KJL**************************************************************************** ************ * The player's AngVelocity as set by the above code is only valid in the player's object * * space, and so has to be rotated into world space. So aliens can walk on the ceiling, etc. * *************************************************************************** *************KJL*/ if (dynPtr->AngVelocity.EulerY) { MATRIXCH mat;

int angle = MUL_FIXED(NormalFrameTime,dynPtr->AngVelocity.EulerY)&4095; int cos = GetCos(angle); int sin = GetSin(angle); mat.mat11 = cos; mat.mat12 = 0; mat.mat13 = -sin; mat.mat21 = 0; mat.mat22 = 65536; mat.mat23 = 0; mat.mat31 = sin; mat.mat32 = 0; mat.mat33 = cos;

Ok, this ###### kind of works! It's buggy but the code is very simple :

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local angles = Angles(0,0,0) local coords = angles:GetCoords() local wallRoll = -math.acos(self.wallWalkingNormalCurrent.y)

I'm not sure how Coords.GetRotation(axis, angle) works, but it works like a rotation matrix (specified by an axis and an angle). So the order in which you multiply matter (found out this one by trial and error :).

There need to more stuff in the computation of wallRoll, because now it works only of the wall on you left (you need to change the sign). I failed to find a way to fix it. I'll post the code when more time is available.

Soul_RiderMod BeanJoin Date: 2004-06-19Member: 29388Members, Constellation, Squad Five Blue

edited January 2012

I may have found the problem. In the wall-walking code, all the rotations are done around the x-axis. This will need to be looked at. :)

Line 315 of Skulk.lua:

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// Prefer spinning around the x axis. if self:GetCoords().xAxis:DotProduct(self.wallWalkingNormalGoal) ~= -1 then normalDiff = self.wallWalkingNormalGoal - self:GetCoords().xAxis else normalDiff = self.wallWalkingNormalGoal - self.wallWalkingNormalCurrent:GetPerpendicular() end<!--c2--></div><!--ec2-->

If you edit that to:

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// Prefer spinning around the x axis. normalDiff = self.wallWalkingNormalGoal - self.wallWalkingNormalCurrent:GetPerpendicular()<!--c2--></div><!--ec2-->

That might solve the problem.

My brain is tired. Nice video Yukki, I can't get the code to work :P

<!--quoteo(post=1897255:date=Jan 25 2012, 06:50 PM:name=Yuuki)--><div class='quotetop'>QUOTE (Yuuki @ Jan 25 2012, 06:50 PM) <a href="index.php?act=findpost&pid=1897255"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Yes but you do the dot product with U=[0,1,0], no ?

[0,1,0] . [0,y,z] = y [0,1,0] . [x,y,0] = y<!--QuoteEnd--></div><!--QuoteEEnd--> No, because N = [x,y,z]; has length = 1 N' = normalised[0,y,z]; has length = 1 N* = normalised[x,y,0]; has length = 1

So if N was [1/sqrt(6), <b>1/sqrt(6)</b>, sqrt(2/3)] N' = normalised[<u>0</u>, <b>1/sqrt(6)</b>, sqrt(2/3)] = [<u>0</u>, <b>1/sqrt(5)</b>, 2/sqrt(5)] N* = normalised[1/sqrt(6), <b>1/sqrt(6)</b>, <u>0</u>] = [1/sqrt(2), <b>1/sqrt(2)</b>, <u>0</u>]

After watching that video, I'm really excited about this.

Also, it may be wise to create a cube room, then a cylindrical room, then a spherical room, to test this sort of thing, before we test (and refine) it on these complex geometries.

<!--quoteo(post=1897366:date=Jan 26 2012, 06:33 AM:name=Yuuki)--><div class='quotetop'>QUOTE (Yuuki @ Jan 26 2012, 06:33 AM) <a href="index.php?act=findpost&pid=1897366"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec--><!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local angles = Angles(0,0,0) local coords = angles:GetCoords() local wallRoll = -math.acos(self.wallWalkingNormalCurrent.y)

angles:BuildFromCoords(coords) self:SetViewAngles(angles)<!--c2--></div><!--ec2--><!--QuoteEnd--></div><!--QuoteEEnd--> I'm curious why you only roll and do not pitch with the wall angle.

Also, am I right in assuming that you have omitted this: <!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec--><!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local viewAngles = Angles(input.pitch, input.yaw, 0) self:SetViewAngles(viewAngles)<!--c2--></div><!--ec2--><!--QuoteEnd--></div><!--QuoteEEnd-->

Going to try working on your code, won't be able to test it though, will paste it here when I'm done.

Also, question, how do camera angles relate to movement? That could be another source of error. (We only want to change the plane of movement with the surface normal, not tie all movement to the player's camera including their mouse inputs.)

roll and pitch: <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local angles = Angles(0,0,0) local coords = angles:GetCoords()

local wallNormal = self.wallWalkingNormalCurrent

local wallNormalRoll = wallNormal wallNormalRoll.z = 0 // z is roll-axis, set z to 0 wallNormalRoll = GetNormalizedVector(wallNormalRoll) // normalise, length = 1

local wallNormalPitch = wallNormal wallNormalPitch.x = 0 // x is pitch-axis, set x to 0 wallNormalPitch = GetNormalizedVector(wallNormalPitch) // normalise, length = 1

local wallRoll = 0 local wallPitch = 0

if wallNormalRoll.x < 0 // when x is negative wallRoll = -math.acos(wallNormalRoll.y) // roll is negative else // when x is positive wallRoll = math.acos(wallNormalRoll.y) // roll is positive end

if wallNormalPitch.z < 0 // when z is negative wallPitch = math.acos(wallNormalPitch.y) // pitch is positive else // when z is positive wallPitch = -math.acos(wallNormalPitch.y) // pitch is negative end

roll only: <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local angles = Angles(0,0,0) local coords = angles:GetCoords()

local wallNormalRoll = self.wallWalkingNormalCurrent wallNormalRoll.z = 0 // z is roll-axis, set z to 0 wallNormalRoll = GetNormalizedVector(wallNormalRoll) // normalise, length = 1

local wallRoll = 0

if wallNormalRoll.x < 0 // when x is negative wallRoll = -math.acos(wallNormalRoll.y) // roll is negative else // when x is positive wallRoll = math.acos(wallNormalRoll.y) // roll is positive end

pitch only: <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local angles = Angles(0,0,0) local coords = angles:GetCoords()

local wallNormalPitch = self.wallWalkingNormalCurrent wallNormalPitch.x = 0 // x is pitch-axis, set x to 0 wallNormalPitch = GetNormalizedVector(wallNormalPitch) // normalise, length = 1

local wallPitch = 0

if wallNormalPitch.z < 0 // when z is negative wallPitch = math.acos(wallNormalPitch.y) // pitch is positive else // when z is positive wallPitch = -math.acos(wallNormalPitch.y) // pitch is negative end

To implement it you need to copy Player:UpdateViewAngles(input) (in player.lua) and put it in skulk.lua (as Skulk:UpdateViewAngles(input)) and modify the function according to above codes.

We could maybe do it in Skulk:PlayerCameraCoordsAdjustment(cameraCoords) instead.

Right about the dot product, I missed the normalization.

For the movement you need to override Player:ComputeForwardVelocity(input).

Just need to change :

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local angles = self:ConvertToViewAngles(input.pitch, input.yaw, 0) local viewCoords = angles:GetCoords()<!--c2--></div><!--ec2-->

To :

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local viewCoords = self:GetCoords()<!--c2--></div><!--ec2-->

>Soul_Rider

I think it's the rotation for the model, could be useful to look at though.

<!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec-->if wallNormalRoll.x < 0 // when x is negative wallRoll = -math.acos(wallNormalRoll.y) // roll is negative else // when x is positive wallRoll = math.acos(wallNormalRoll.y) // roll is positive end<!--QuoteEnd--></div><!--QuoteEEnd-->

I don't think it's gonna work, wallNormalRoll.x is invariant but when you turn your view around the wall on your left is on your right.

e.g. <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local angles = Angles(0,0,0) local coords = angles:GetCoords()

local wallNormal = self.wallWalkingNormalCurrent local offset = math.acos(self:GetCoords().z) // angle between personal z axis and absolute z axis wallNormal = wallNormal*Coords.GetRotation(wallNormal.yAxis, offset) // rotate around y axis

local wallNormalRoll = wallNormal ... angles:BuildFromCoords(coords) self:SetViewAngles(angles)<!--c2--></div><!--ec2--> Not sure if I have the right vector, self:GetCoords(), and not sure if offset should be positive or negative.

Actually, I'm curious why we don't just initialise <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local angles = self:ConvertToViewAngles(input.pitch, input.yaw, 0)<!--c2--></div><!--ec2--> instead of <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local angles = Angles(0,0,0)<!--c2--></div><!--ec2--> and then not need to bother doing these rotations: <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->*Coords.GetRotation(coords.yAxis, input.yaw)*Coords.GetRotation(coords.xAxis, input.pitch)<!--c2--></div><!--ec2--> and only do the necessary rotations for the wall normal.

e.g. <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local angles = self:ConvertToViewAngles(input.pitch, input.yaw, 0) local coords = angles:GetCoords()

local wallNormal = self.wallWalkingNormalCurrent local offset = math.acos(self:GetCoords().z) // angle between personal z axis and absolute z axis wallNormal = wallNormal*Coords.GetRotation(wallNormal.yAxis, offset) // rotate around y axis

local wallNormalRoll = wallNormal wallNormalRoll.z = 0 // z is roll-axis, set z to 0 wallNormalRoll = GetNormalizedVector(wallNormalRoll) // normalise, length = 1

local wallNormalPitch = wallNormal wallNormalPitch.x = 0 // x is pitch-axis, set x to 0 wallNormalPitch = GetNormalizedVector(wallNormalPitch) // normalise, length = 1

local wallRoll = 0 local wallPitch = 0

if wallNormalRoll.x < 0 // when x is negative wallRoll = -math.acos(wallNormalRoll.y) // roll is negative else // when x is positive wallRoll = math.acos(wallNormalRoll.y) // roll is positive end

if wallNormalPitch.z < 0 // when z is negative wallPitch = math.acos(wallNormalPitch.y) // pitch is positive else // when z is positive wallPitch = -math.acos(wallNormalPitch.y) // pitch is negative end

Looks just like what I've been hoping for! Strange thing about the right wall, does it happen if you go on the left wall and aim so that it become your right wall too? At one point in the video it seemed like the roll behaved as if the wall was a ceiling instead of floor, i.e a sign wrong somewhere.

Out of curiosity, why are all the codes following this so much more complex then the initial ones?

Soul_RiderMod BeanJoin Date: 2004-06-19Member: 29388Members, Constellation, Squad Five Blue

<!--quoteo(post=1897492:date=Jan 26 2012, 08:14 PM:name=Fluid Core)--><div class='quotetop'>QUOTE (Fluid Core @ Jan 26 2012, 08:14 PM) <a href="index.php?act=findpost&pid=1897492"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Looks just like what I've been hoping for! Strange thing about the right wall, does it happen if you go on the left wall and aim so that it become your right wall too? At one point in the video it seemed like the roll behaved as if the wall was a ceiling instead of floor, i.e a sign wrong somewhere.

Out of curiosity, why are all the codes following this so much more complex then the initial ones?<!--QuoteEnd--></div><!--QuoteEEnd-->

Because we are trying to do something a more complex than the original code allows :) To enable wallwalking the player is rotated around the x-axis. This causes issues when you want to go upside down, and is causing the current issue of of keys being reversed on the walls. This new code is written to solve the key changes, but only works on one wall :)

Good question there Fluid. If you are walking on the left wall, then about face so you are looking behind you, does the view flip so you become attached by the head?

<!--quoteo(post=1897512:date=Jan 26 2012, 10:50 PM:name=Soul_Rider)--><div class='quotetop'>QUOTE (Soul_Rider @ Jan 26 2012, 10:50 PM) <a href="index.php?act=findpost&pid=1897512"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Because we are trying to do something a more complex than the original code allows :) To enable wallwalking the player is rotated around the x-axis. This causes issues when you want to go upside down, and is causing the current issue of of keys being reversed on the walls. This new code is written to solve the key changes, but only works on one wall :)

Good question there Fluid. If you are walking on the left wall, then about face so you are looking behind you, does the view flip so you become attached by the head?<!--QuoteEnd--></div><!--QuoteEEnd-->

Well, I mean compared to the code Yuuki added with the left wall working video. I can tell for sure that the inouts work there; it is very hard to walk and look around when your yaw pitches and your pitch yawes. :)

Soul_RiderMod BeanJoin Date: 2004-06-19Member: 29388Members, Constellation, Squad Five Blue

<!--quoteo(post=1897460:date=Jan 26 2012, 05:18 PM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Jan 26 2012, 05:18 PM) <a href="index.php?act=findpost&pid=1897460"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec--><!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local angles = self:ConvertToViewAngles(input.pitch, input.yaw, 0) local coords = angles:GetCoords()

local wallNormal = self.wallWalkingNormalCurrent local offset = math.acos(self:GetCoords().z) // angle between personal z axis and absolute z axis wallNormal = wallNormal*Coords.GetRotation(wallNormal.yAxis, offset) // rotate around y axis

local wallNormalRoll = wallNormal wallNormalRoll.z = 0 // z is roll-axis, set z to 0 wallNormalRoll = GetNormalizedVector(wallNormalRoll) // normalise, length = 1

local wallNormalPitch = wallNormal wallNormalPitch.x = 0 // x is pitch-axis, set x to 0 wallNormalPitch = GetNormalizedVector(wallNormalPitch) // normalise, length = 1

local wallRoll = 0 local wallPitch = 0

if wallNormalRoll.x < 0 // when x is negative wallRoll = -math.acos(wallNormalRoll.y) // roll is negative else // when x is positive wallRoll = math.acos(wallNormalRoll.y) // roll is positive end

if wallNormalPitch.z < 0 // when z is negative wallPitch = math.acos(wallNormalPitch.y) // pitch is positive else // when z is positive wallPitch = -math.acos(wallNormalPitch.y) // pitch is negative end

Well, a lot of this is up in the air. Tell me what you think.<!--QuoteEnd--></div><!--QuoteEEnd-->

The problem with this code is that

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local offset = math.acos(self:GetCoords().z) // angle between personal z axis and absolute z axis<!--c2--></div><!--ec2-->

Will always return 0. I tried adding an if clause, if self:GetIsWallWalking() then, but that just delayed the problem until I hit the wall. You are trying to work out the degree of rotation before there is any rotation.

The original roll and pitch code needs 'then' added after each of the 2 if statements. This code will run, but it doesn't solve the problems.

Also, it doesn't like going upside down. If you strafe up a curved wall to the left, you hit 90 degrees and stop. The mouse keys have not changed their directional binding at all, so from a 90 degree wall, pressing right is now 'up' ie still world right. Mouse movement is also rotated wrongly.

Taking a step back a moment we have:

A camera orientation A world orientation An input orientation

Currently the input orientation is tied to the world, and we need it tied to camera/player orientation.

Would it not be better if we applied the code to Skulk:PlayerCameraCoordsAdjustment(cameraCoords) and made an input rotation based on that? This way we also know the rotations have already happened, as we are now applying the camera adjustments.

Been working on this for hours, turning my brain off now....

@Soul Rider: I think he was talking about 'initial' code as in Yuuki's code seen in that video, rather than the original wallwalking code.

@Fluid Core+: This is the process that I've implemented, and it explains why the new implementation is so much more involved than the original implementation:

- First, you essentially rotate the absolute axes around the y-axis to match up with the player's personal x and z axes (you need a vector that does so: one whose x and z change with the player's orientation, but whose y remains parallel with the absolute y axis; I'm hoping that this is <b>self:getCoords()</b>). This is achieved by taking the inverse cosine of the dot product between the two z-axes i.e. with both being unit vectors: [0,0,1].[x,0,z] = z, conveniently. So <i><b>offset = arccos(z)</b></i>. <a href="http://www.wolframalpha.com/input/?i=arccos%28x%29*180%2Fpi+from+-1+to+1" target="_blank">http://www.wolframalpha.com/input/?i=arcco...pi+from+-1+to+1</a>

- Next, you take the wallNormal with respect to the modified axis above. -> Actually, rotating the axis and taking the wallnormal with respect to the modified axis would be far too difficult to implement, so you instead do the two aforementioned steps the other way around: take the <b>wallNormal</b> (<b>self.wallWalkingNormalCurrent</b>) with respect to the absolute axes, then rotate it (<b>*Coords.GetRotation(wallNormal.yAxis, offset)</b>) to match the above axis. Now we have a good starting point.

- Next, you essentially flatten the wallNormal [x,y,z] into the x-y plane (for roll, z = 0, z is roll-axis) and the z-y plane (for pitch, x = 0, x is pitch-axis); I've called these <b>wallNormalRoll</b> [x,y,0] and <b>wallNormalPitch</b> [0,y,z] respectively.

- Next, you want to find the angle between absolute "up" [0,1,0] and <b>wallNormalRoll</b>, to determine the roll; and the angle between absolute "up" [0,1,0] and <b>wallNormalPitch</b>, to determine the pitch. This is achieved by taking the inverse cosine of the dot product between two unit vectors. First, you have to normalise <b>wallNormalRoll</b> and <b>wallNormalPitch</b> using <b>GetNormalizedVector()</b>: <b>wallNormalRoll</b> [x',y',0]; <b>wallNormalPitch</b> [0,y*,z*]. Now you can take the dot products: [0,1,0].[x',y',0] = y', conveniently; [0,1,0].[0,y*,z*] = y*, conveniently.

- Because the roll/pitch angles are independent of the horizontal (x-z plane) direction of the wall, you have to add dependencies. -> If you approach a wall from the left (strafe left into the wall), then <b>wallNormal</b> faces your right, so x is positive, and you want to roll right (positive roll); if you approach a wall from the right (strafe right into the wall), then <b>wallNormal</b> faces your left, so x is negative, and you want to roll left (negative roll). Remember that roll is calculated in the x-y plane, but using y alone. So when x is negative, roll is negative, and when when x is positive, roll is positive. -> If you approach a wall from the front (walk back into the wall), then <b>wallNormal</b> faces forward, so z is positive, and you want to pitch down (negative pitch); if you approach a wall from behind (walk forward into the wall), then <b>wallNormal</b> faces backward, so z is negative, and you want to pitch up (positive pitch). Remember that pitch is calculated in the z-y plane, but using y alone. So when z is negative, pitch is positive, and when z is positive, pitch is negative. -> Of course, this is dependent on <u>your</u> personal x and z axes, so you have to account for that, and this is what we already did in the first (or second, really) step.

- So, with the above two steps: -> <i><b>if x < 0, wallRoll = -arccos(y')</b></i> -> <i><b>if x >= 0, wallRoll = arccos(y')</b></i> -> <i><b>if z < 0, wallPitch = arccos(y*)</b></i> -> <i><b>if z >= 0, wallPitch = -arccos(y*)</b></i>

- Rotate the player's view (<b>coords</b>) around the roll-axis using the roll angle (<b>wallRoll</b>), and rotate the player's view (<b>coords</b>) around the pitch-axis using the pitch angle (<b>wallPitch</b>): <b>coords*Coords.GetRotation(coords.zAxis, wallRoll)*Coords.GetRotation(coords.xAxis, wallPitch)</b>

- Then as a last step, you rotate the player's view around the pitch-axis and the yaw-axis using the <b>input.pitch</b> and <b>input.yaw</b> angles respectively: <b>coords*...*Coords.GetRotation(coords.yAxis, input.yaw)*Coords.GetRotation(coords.xAxis, input.pitch)</b> -> OR you initialise the player's view with <b>input.pitch</b> and <b>input.yaw</b> <i>before</i> the <u>first</u> step: <b>local angles = self:ConvertToViewAngles(input.pitch, input.yaw, 0) local coords = angles:GetCoords()</b> * not sure this will work as intended.

Contrast this with Yuuki's original implementation:

- Take the angle between the <i><u>unflattened</u></i> <b>wallNormal</b> [x,y,z] and absolute "up" [0,1,0], using the inverse cosine of the dot product of both unit vectors: [x,y,z].[0,1,0] = y. Taking the inverse cosine gives the angle. -> Set this angle as the roll angle, <i><b>wallRoll = arccos(y)</i></b>

- Rotate the player's view (coords) around the roll-axis using the roll angle: <b>coords*Coords.GetRotation(coords.zAxis, wallRoll)</b>

- Then as a last step, you rotate the player's view around the pitch-axis and the yaw-axis using the <b>input.pitch</b> and <b>input.yaw</b> angles respectively: <b>coords*...*Coords.GetRotation(coords.yAxis, input.yaw)*Coords.GetRotation(coords.xAxis, input.pitch)</b> -> OR you initialise the player's view with <b>input.pitch</b> and <b>input.yaw</b> <i>before</i> the <u>first</u> step: <b>local angles = self:ConvertToViewAngles(input.pitch, input.yaw, 0) local coords = angles:GetCoords()</b> * not sure this will work as intended.

<!--quoteo(post=1897566:date=Jan 27 2012, 09:00 AM:name=Soul_Rider)--><div class='quotetop'>QUOTE (Soul_Rider @ Jan 27 2012, 09:00 AM) <a href="index.php?act=findpost&pid=1897566"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->The problem with this code is that

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local offset = math.acos(self:GetCoords().z) // angle between personal z axis and absolute z axis<!--c2--></div><!--ec2-->

Will always return 0.<!--QuoteEnd--></div><!--QuoteEEnd--> Okay, so self:GetCoords() is the wrong vector. We need a different vector. This is the criteria: <!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec-->First, you essentially rotate the absolute axes around the y-axis to match up with the player's personal x and z axes (you need a vector that does so: one whose x and z change with the player's orientation, but whose y remains parallel with the absolute y axis; I'm hoping that this is <b>self:getCoords()</b>).<!--QuoteEnd--></div><!--QuoteEEnd--> Could you take the world orientation (that is, the orientation of the player in the world), the model orientation, the orientation based on input.pitch and input.yaw, or the state in the previous frame?

So what you basically do is cameracords dot wall normal? Then you shouldn't need any if:s and it's generally implemented. Example: strafing into right wall give dotproduct to be 1 (normalized) and your view rotates counter clockwise. Keeping strafing right to ceiling again gives dotproduct as 1 and it rotates as much in the same direction. Going left gives negative dot products, and so rotate clockwise. That should give rotation direction. For angle amplitude just do as you did should work. To tired to figure out a nice dot or cross product now. Oh, and I'm still a fan of just rotating around the z-axis with no change in pitch or yaw as you change surface, just as seen in the awsome video yuuki made!

Hey! It should probably work to replace the minus sign in the wallroll calculation Yuuki did with cameracoords dot wallnormal. But I'm on my phone so can't test it. But maybe I can sleep now :)

<!--quoteo(post=1897575:date=Jan 27 2012, 10:15 AM:name=Fluid Core)--><div class='quotetop'>QUOTE (Fluid Core @ Jan 27 2012, 10:15 AM) <a href="index.php?act=findpost&pid=1897575"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->So what you basically do is cameracords dot wall normal? Then you shouldn't need any if:s and it's generally implemented.<!--QuoteEnd--></div><!--QuoteEEnd--> It doesn't work that way.

I'm too tired to explain, so allow me to drop the (incomplete <!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->(I reached a conclusion and put it away)<!--sizec--></span><!--/sizec-->) wall of text and numbers I used to work this out: Keep in mind that dot product = [x,y,z]*[0,1,0] = y <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->Normal Vector Orientation Roll goal Pitch goal (straight scenarios: pointing towards the face-centres of a cube) 1[ 0, 1, 0] up roll = 0 pitch = 0 2[ 0,-1, 0] ??down roll = 180 pitch = 0 OR roll = 0 pitch = 180 OR roll = 180 pitch = 180 3[ 1, 0, 0] right roll = 90 pitch = 0 4[-1, 0, 0] left roll = -90 pitch = 0 5[ 0, 0, 1] forward roll = 0 pitch = -90 6[ 0, 0,-1] backward roll = 0 pitch = 90 (transitional scenarios 1: pointing towards the edge-centres of a cube) * b = sqrt(1/2) 1-3[ b, b, 0] up-right roll = 45 pitch = 0 1-4[-b, b, 0] up-left roll = -45 pitch = 0 2-3[ b,-b, 0] down-right roll = 135 pitch = 0 2-4[-b,-b, 0] down-left roll = -135 pitch = 0 1-5[ 0, b, b] up-forward roll = 0 pitch = -45 1-6[ 0, b,-b] up-backward roll = 0 pitch = 45 2-5[ 0,-b, b] down-forward roll = 0 pitch = -135 2-6[ 0,-b,-b] down-backward roll = 0 pitch = 135 3-5[ b, 0, b] ??right-forward roll = 45 pitch = -90 OR roll = 90 pitch = -45 3-6[ b, 0,-b] ??right-backward roll = 45 pitch = 90 OR roll = 90 pitch = 45 4-5[-b, 0, b] ??left-forward roll = -45 pitch = -90 OR roll = -90 pitch = -45 4-6[-b, 0,-b] ??left-backward roll = -45 pitch = 90 OR roll = -90 pitch = 45 (transitional scenarios 2: pointing towards the vertexes of a cube) * c = sqrt(1/3) 1-3-5[ c, c, c] up-right-forward roll = 45 pitch = -45 1-3-6[ c, c,-c] up-right-backward roll = 45 pitch = 45 1-4-5[-c, c, c] up-left-forward roll = -45 pitch = -45 1-4-6[-c, c,-c] up-left-backward roll = -45 pitch = 45 2-3-5[ c,-c, c] ??down-right-forward roll = 135 pitch = -45 OR roll = 45 pitch = -135 2-3-6[ c,-c,-c] ??down-right-backward roll = 135 pitch = 45 OR roll = 45 pitch = 135 2-4-5[-c,-c, c] ??down-left-forward roll = -135 pitch = -45 OR roll = -45 pitch = -135 2-4-6[-c,-c,-c] ??down-left-backward roll = -135 pitch = 45 OR roll = -45 pitch = 135

1 roll = +-arccos( 1) pitch = +-arccos( 1) 2a roll = ??arccos(-1) pitch = +-arccos( 1) 2b roll = +-arccos( 1) pitch = ??arccos(-1) 3 roll = +arccos( 0) pitch = +-arccos( 1) 4 roll = -arccos( 0) pitch = +-arccos( 1) 5 roll = +-arccos( 1) pitch = -arccos( 0) 6 roll = +-arccos( 1) pitch = +arccos( 0)

1-3 roll = +arccos( b) pitch = +-arccos( 1) 1-4 roll = -arccos( b) pitch = +-arccos( 1) 2-3 roll = +arccos(-b) pitch = +-arccos( 1) 2-4 roll = -arccos(-b) pitch = +-arccos( 1) 1-5 roll = +-arccos( 1) pitch = -arccos( b) 1-6 roll = +-arccos( 1) pitch = +arccos( b) 2-5 roll = +-arccos( 1) pitch = -arccos(-b) 2-6 roll = +-arccos( 1) pitch = +arccos(-b) 3-5a 3-6a 4-5a 4-6a<!--c2--></div><!--ec2--> Look at 1-3 compared to 1-4, and 2-3 compared to 2-4. 1-3 and 1-4 have the same dot product: b, but you want to roll in different directions. 2-3 and 2-4 have the same dot product: -b, but you want to roll in different directions.

Actually, have a picture: <a href="http://imageshack.us/photo/my-images/37/4scenariosnot2.png/" target="_blank"><img src="http://img37.imageshack.us/img37/7879/4scenariosnot2.th.png" border="0" class="linked-image" /></a>

Uploaded with <a href="http://imageshack.us" target="_blank">ImageShack.us</a> Basically: 4 scenarios not 2. I really have to get going now.

@Soul_Rider, the transformation matrix is already in the game and we've been using it in our code: Vector*Coords.GetRotation(axis, angle)

A really good game to draw inspiration from is Warsow. It's a Quake3/Unreal Tournament mishmash with a heavy emphasis on movement. Obviously, NS2 should not fully imitate it, but it has very fluid and "movie quality" movement including walljumping and short dashes on the ground.

Here's an <a href="http://www.youtube.com/watch?v=8qFizuMoSbM" target="_blank">example</a>.

I would really love to see a toned down version of that movement in this game someday.

<!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec-->Would it not be better if we applied the code to Skulk:PlayerCameraCoordsAdjustment(cameraCoords) and made an input rotation based on that? This way we also know the rotations have already happened, as we are now applying the camera adjustments.<!--QuoteEnd--></div><!--QuoteEEnd-->

I think it's simpler to build up everything from scratch.

Maybe we should continue this into the skulk view rotation topic, and keep this one for more general discussion ?

## Comments

2007-12-26Member:63260Members, Reinforced - ShadowGiven :

- The three angles of the camera: <b>(pitch, yaw, roll)</b>

- The wall normal: <b>n</b>

- Two mouse input angles: <b>input.yaw</b> and <b>input.pitch</b>

Write an expression for <b>(pitch, yaw, roll)=f(n,input.y,input.p)</b>.

Note that n = n(t) and that df / dn should be smartly chosen (i.e. you don't want changes in the wall normal to make crazy changes in the camera angles).<!--QuoteEnd--></div><!--QuoteEEnd-->

Don't forget the movement inputs too, input.x. Oh, and I assume you mean input.y as input.yaw, but input.y is something else...

2010-11-20Member:75079MembersDon't need to worry about the movement, it's easy once the camera is set (because you can use the camera axis).

2007-12-26Member:63260Members, Reinforced - ShadowDon't need to worry about the movement, it's easy once the camera is set (because you can use the camera axis).<!--QuoteEnd--></div><!--QuoteEEnd-->

Ah, nice. I meant before when we tried to transform the coordinates we transformed input.x into input.y and that was the normal up-down direction, or strafing on a wall with view rotation. Would be bad to not get it working because of confusing one with the other :)

I didn't know you could relate it to the camera axis, that sounds quite easy to do. With that, can't you do a quick fix for wallwalking changeing wallwalking tilt to 1 and then relate the inputs to the camera? That should make it working smoothly for walls atleast then. Probably need to change tilt speed too.

2007-12-24Member:63250Members<!--coloro:#AAAAFF--><span style="color:#AAAAFF"><!--/coloro-->- camera.absolute = (0,0,0) // this would be your default camera with up as "absolute y", right as "absolute x", forward as "absolute z"

- camera.reference = f(U, n, camera.absolute)

- camera.current = f(input.pitch, input.yaw, camera.reference)

- U{absolute} = [0,1,0] // "up"

- n{relative} = f("surface") = [x,y,z] // the surface normal, a unit vector<!--colorc--></span><!--/colorc-->

<!--coloro:#AAFFAA--><span style="color:#AAFFAA"><!--/coloro-->> n' = normalise[0,y,z] // x is pitch axis

> n* = normalise[x,y,0] // z is roll axis

> camera.reference(pitch) = camera.absolute(pitch) + arccos(U.n') // dot product for angle between normal and up; unsure if this will yield positive or negative correctly

> camera.reference(yaw) = camera.absolute(yaw)

> camera.reference(roll) = camera.absolute(roll) + arccos(U.n*) // dot product for angle between normal and up; unsure if this will yield positive or negative correctly<!--colorc--></span><!--/colorc-->

<!--coloro:green--><span style="color:green"><!--/coloro-->>/ camera.current(pitch) = camera.reference(pitch) + f(input.pitch,input.yaw) // ???

>/ camera.current(yaw) = camera.reference(yaw) + f(input.pitch,input.yaw) // ???

>/ camera.current(roll) = camera.reference(roll) + f(input.pitch,input.yaw) // ???<!--colorc--></span><!--/colorc-->

The next step is to apply input.pitch and input.yaw (which are relative) to camera(pitch,yaw,roll) (which is absolute). I'm hoping there is a built-in function that does this, otherwise it could be problematic.

Because, for example, if

a) camera.reference(roll) = 0deg (clockwise), +input.pitch (up, relative) => up (absolute, +pitch), +input.yaw (right, relative) => right (absolute, +yaw)

<!--coloro:gray--><span style="color:gray"><!--/coloro--><!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->// +pitch = +1*input.pitch +0*input.yaw

// +yaw = +1*input.yaw +0*input.pitch<!--sizec--></span><!--/sizec--><!--colorc--></span><!--/colorc-->

b) camera.reference(roll) = 90deg (clockwise), +input.pitch (up, relative) => left (absolute, -yaw), +input.yaw (right, relative) => down (absolute, -pitch)

<!--coloro:gray--><span style="color:gray"><!--/coloro--><!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->// +pitch = +0*input.pitch -1*input.yaw

// +yaw = +0*input.yaw -1*input.pitch<!--sizec--></span><!--/sizec--><!--colorc--></span><!--/colorc-->

c) camera.reference(roll) = 180deg (clockwise), +input.pitch (up, relative) => down (absolute, -pitch), +input.yaw (right, relative) => left (absolute, -yaw)

<!--coloro:gray--><span style="color:gray"><!--/coloro--><!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->// +pitch = -1*input.pitch +0*input.yaw

// +yaw = -1*input.yaw +0*input.pitch<!--sizec--></span><!--/sizec--><!--colorc--></span><!--/colorc-->

camera.reference(roll) = 270deg (clockwise), +input.pitch (up, relative) => right (absolute, +yaw), +input.yaw (right, relative) => up (absolute, +pitch)

<!--coloro:gray--><span style="color:gray"><!--/coloro--><!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->// +pitch = +0*input.pitch +1*input.yaw

// +yaw = +0*input.yaw +1*input.pitch<!--sizec--></span><!--/sizec--><!--colorc--></span><!--/colorc-->

Interestingly, those numbers follow:

<!--coloro:gray--><span style="color:gray"><!--/coloro-->absolute pitch = cos(camera.reference(roll))*input.pitch - sin(camera.reference(roll))*input.yaw

absolute yaw = cos(camera.reference(roll))*input.yaw - sin(camera.reference(roll))*input.pitch<!--colorc--></span><!--/colorc-->

So, assuming all my guesstimation is correct:

<!--coloro:#AAFFAA--><span style="color:#AAFFAA"><!--/coloro-->>/ camera.current(pitch) = camera.reference(pitch) + cos(camera.reference(roll))*input.pitch - sin(camera.reference(roll))*input.yaw

>/ camera.current(yaw) = camera.reference(yaw) + cos(camera.reference(roll))*input.yaw - sin(camera.reference(roll))*input.pitch

>/ camera.current(roll) = camera.reference(roll)<!--colorc--></span><!--/colorc-->

But then it doesn't consider when camera.reference(yaw) and camera.reference(pitch) are not 0, which would lead to a similar sort of thing, with a similar sort of solution. I'm too lazy to work it out right now.

Would any of this work? I don't know.

2010-11-20Member:75079MembersWhat we want is to express pitch in function of n and input, like pitch = f(n,input), if I rewrite what you found in theses terms I get :

pitch = camera.absolute.pitch + arccos(U.n') + cos(camera.reference(roll))*input.pitch - sin(camera.reference(roll))*input.yaw

Also I can rewrite : arccos(U.n') = arccos(U.n*) = arccos(y) = wallAngle

pitch = camera.absolute.pitch + wallAngle + cos(wallAngle+camera.absolute.roll)*input.pitch - sin(wallAngle+camera.absolute.roll)*input.yaw

I'm also a bit confused about camera.absolute and camera.reference.

2010-11-20Member:75079Members1) Imagine you face a vertical wall, you don't touch your mouse and walk straight to the wall, shouldn't the camera rotate of 90 degrees around the xAxis ? You would get your yAxis aligned with the wall normal and your zAxis pointing the ceiling.

2) Imagine you are perpendicular to vertical wall, you don't touch your mouse and strafe toward the wall, shouldn't the camera rotate of 90 degrees around the zAxis this time ?

So the function should also take into account the current orientation :

new pitch = f(n,input.yaw,input.pitch, current.yaw, current.pitch, current.roll)

Theses vectorial stuff destroy my brain :)

<a href="http://en.wikipedia.org/wiki/Rotation_matrix#Rotations_in_three_dimensions" target="_blank">http://en.wikipedia.org/wiki/Rotation_matr...hree_dimensions</a>

2007-12-26Member:63260Members, Reinforced - Shadow2) Imagine you are perpendicular to vertical wall, you don't touch your mouse and strafe toward the wall, shouldn't the camera rotate of 90 degrees around the zAxis this time ?<!--QuoteEnd--></div><!--QuoteEEnd-->

Well, we don't want 1). If you aim straight to the wall then you should keep aiming straight to the wall, and be about to walk up on the wall but aiming "down" into the wall. So if you pitch up you should aim up towards the ceiling, if you yaw then your view should just rotate while you keep staring "down" at the wall. If it doesn't behave like this (like in BallMan and my experience from AvP2) then you can't keep your aim at a certain point as you transition between different surfaces.

2007-12-24Member:63250MembersUh, I didn't really use any official notation, since I'm not sure what they would be.

But, essentially:

Camera.X is an array that has the parameters pitch, yaw and roll, like (pitch,yaw,roll); so Camera.X(pitch) would just be the value of pitch.

Vector is an array that has the parameters x, y and z, like [x,y,z]; so Vector[x] would just be the value of x. I guess I was inconsistent with this though.

There are three different camera arrays:

camera.absolute (your starting point camera based on a flat horizontal plane; that is, the normal is parallel with the absolute y-axis)

camera.reference (your camera offset, due to the wall angle(s), with reference to the absolute axis)

camera.current (your camera based on your inputs)

I don't actually know the in-game implementation, so I've been approaching it from a logical perspective, using intermediates.

camera.absolute = (0,0,0) => pitch = 0, yaw = 0, roll = 0

As I explained in less detail earlier, it corresponds to when:

- subjective "right" = absolute "x-axis"; the x-axis is the pitch axis

- subjective "up" = absolute "y-axis"; the y-axis is the yaw axis

- subjective "forward" = absolute "; the z-axis is the roll axis

camera.reference depends on the wall angle(s) (specifically, the wall-normal). Your subjective "right", "up" and "forward" change with the surface.

I'm not even sure if the game uses states or deltas. All my work assumes states, but you can simply take the difference for deltas.

Also,

arccos(U.n') != arccos(U.n*)

n' is a component vector (sort of) of the wall normal in the z-(forward)direction (that is, setting x to zero); therefore the angle between it and absolute "up" gives "pitch".

n* is a component vector (sort of) of the wall normal in the x-(right)direction (that is, setting z to zero); therefore the angle between it and absolute "up" gives roll.

<i><!--coloro:grey--><span style="color:grey"><!--/coloro-->Both situations you described, 1 and 2, should work with my implementation (assuming it can be implemented).

0) You are on the ground, your normal starts at [0,1,0], your camera is (0,0,0).

1) When walking ahead onto to a wall in front of you, your normal changes to [0,0,-1], your camera is (90,0,0). Pitched up, or pitched backward, 90 degrees.

2) When strafing onto a wall to your right, your normal changes to [-1,0,0], your camera is (0,0,-90) or (0,0,270). Rolled to the left, 90 degrees.

3) When strafing onto a wall to your left, your normal changes to [1,0,0], your camera is (0,0,90). Rolled to the right, 90 degrees.

4) From 1), when walking onto the ceiling, your normal becomes [0,-1,0], your camera is (180,0,180). Upside down. Pitched up, or pitched backward, 180 degrees; and rolled to the right 180 degrees.

5) From 2) or 3), when walking onto the ceiling, your normal becomes [0,-1,0], your camera is (180,0,180). Upside down. Pitched up, or pitched backward, 180 degrees; and rolled to the right 180 degrees.

Note that 4 and 5 describe the same situation (upside down), and it changes both pitch and roll parameters. This is because yaw is independent of the surface normal.<!--colorc--></span><!--/colorc--></i> This needs to be checked.

I'm actually not 100% sure about whether or not yaw goes into it. I suppose you could take the (which?) perpendicular of the surface normal and compare that against [1,0,0], taking the difference in angle as the reference yaw.

Also note, the last section is incomplete. I've only, so far, considered when roll is kept constant, I haven't considered when pitch and yaw are constant. My hope was that the game wouldn't need it. Essentially it should be able to add a subjective pitch,yaw,roll (your input) to the reference pitch, yaw, roll (your camera based on the surface). Kind of like how your position in the game world is based on an absolute reference, but you yourself have personal x,y,z-axes.

<!--quoteo(post=1897075:date=Jan 24 2012, 07:18 PM:name=Yuuki)--><div class='quotetop'>QUOTE (Yuuki @ Jan 24 2012, 07:18 PM) <a href="index.php?act=findpost&pid=1897075"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->new pitch = f(n,input.yaw,input.pitch, current.yaw, current.pitch, current.roll)<!--QuoteEnd--></div><!--QuoteEEnd-->

Seems like the game wants deltas. In which case you need to change the parameters to:

new pitch = f(current.n,new.n,input.yaw,input.pitch, current.yaw, current.pitch, current.roll)

new yaw = f(current.n,new.n,input.yaw,input.pitch, current.yaw, current.pitch, current.roll)

new roll = f(current.n,new.n,input.yaw,input.pitch, current.yaw, current.pitch, current.roll)

Basically you need to:

1) consider the (which is what I've done with camera.reference, though it's probably buggy).

I'm assuming that pitch/yaw/roll are based on an absolute reference: when

<!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec-->- subjective "right" = absolute "x-axis"; the x-axis is the pitch axis

- subjective "up" = absolute "y-axis"; the y-axis is the yaw axis

- subjective "forward" = absolute "; the z-axis is the roll axis<!--QuoteEnd--></div><!--QuoteEEnd-->

Also, I want to know, can the game add:

<i>Absolute (Pitch, Yaw, Roll) + Transformed(Subjective (Pitch, Yaw, Roll))</i>

the same way that the game can add

<i>Absolute (x, y, z) + Transformed(Subjective (x, y, z))</i>

For example, if you're at [1,0,0] (right of the origin), you're facing in the direction [0,1,0] (the "up" direction), and you <b>subjectively</b> move [0,0,2] (forward, in the "up" direction), you <b>absolutely</b> move [0,2,0] ("in the up direction"), and end up at [1,2,0]. The game appears to do this on its own. Can you do the same thing with pitch, yaw, roll?

Can you point out the code that governs this sort of stuff for the ground-based implementation? It would give us a much better idea.

Just looked at the wikipedia article you linked.

... Jesus.

2010-11-20Member:75079Members<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> // Update to the current view angles.

local viewAngles = Angles(input.pitch, input.yaw, 0)

self:SetViewAngles(viewAngles)<!--c2--></div><!--ec2-->

So all we need to do is to rewrite it :

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> pitch = ...

yaw = ...

roll = ...

local viewAngles = Angles(pitch, yaw, roll)

self:SetViewAngles(viewAngles)<!--c2--></div><!--ec2-->

We just need to write down the mathematical expression for " ... ".

I kind of follow what you do but still I'm a bit confused. Do you have an expression for "..." ?

<!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec-->arccos(U.n') != arccos(U.n*)<!--QuoteEnd--></div><!--QuoteEEnd-->

Yes but you do the dot product with U=[0,1,0], no ?

[0,1,0] . [0,y,z] = y

[0,1,0] . [x,y,0] = y

A last note about programing, we can easily transform angles into coordinates (the three axis representation) and coordinates into angles. Maybe it's easier to think in axis.

2004-06-19Member:29388Members, Constellation, Squad Five BlueBreaking down the file Pmove.c, I have the code used for setting the vector direction of the player, so it can jump in the direction it is looking at:

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->

if (notTooSteep)

{

/* alien can jump in the direction it's looking */

if (AvP.PlayerType == I_Alien)

{

VECTORCH viewDir;

viewDir.vx = Global_VDB_Ptr->VDB_Mat.mat13;

viewDir.vy = Global_VDB_Ptr->VDB_Mat.mat23;

viewDir.vz = Global_VDB_Ptr->VDB_Mat.mat33;

if ((playerStatusPtr->ShapeState == PMph_Crouching) && (DotProduct(&viewDir,&dynPtr->GravityDirection)<-32768))

{

dynPtr->LinImpulse.vx += MUL_FIXED(viewDir.vx,jumpSpeed*3);

dynPtr->LinImpulse.vy += MUL_FIXED(viewDir.vy,jumpSpeed*3);

dynPtr->LinImpulse.vz += MUL_FIXED(viewDir.vz,jumpSpeed*3);

}

else

{

dynPtr->LinImpulse.vx -= MUL_FIXED(dynPtr->GravityDirection.vx,jumpSpeed);

dynPtr->LinImpulse.vy -= MUL_FIXED(dynPtr->GravityDirection.vy,jumpSpeed);

dynPtr->LinImpulse.vz -= MUL_FIXED(dynPtr->GravityDirection.vz,jumpSpeed);

dynPtr->LinVelocity.vz += jumpSpeed;

}

dynPtr->TimeNotInContactWithFloor = -1;

}

else

{

dynPtr->LinImpulse.vx -= MUL_FIXED(dynPtr->GravityDirection.vx,jumpSpeed);

dynPtr->LinImpulse.vy -= MUL_FIXED(dynPtr->GravityDirection.vy,jumpSpeed);

dynPtr->LinImpulse.vz -= MUL_FIXED(dynPtr->GravityDirection.vz,jumpSpeed);

dynPtr->TimeNotInContactWithFloor = 0;

}<!--c2--></div><!--ec2-->

Then there is the follow-up code to translate the local player change to the world...

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->

/* rotate LinVelocity into world space */

RotateVector(&dynPtr->LinVelocity,&dynPtr->OrientMat);

}

/* zero angular velocity */

dynPtr->AngVelocity.EulerX = 0;

dynPtr->AngVelocity.EulerZ = 0;

if(playerStatusPtr->Mvt_InputRequests.Flags.Rqst_Strafe)

{

dynPtr->AngVelocity.EulerY = 0;

}

else

{

dynPtr->AngVelocity.EulerY = turnSpeed;

}

playerStatusPtr->ForwardInertia = forwardSpeed;

playerStatusPtr->StrafeInertia = strafeSpeed;

playerStatusPtr->TurnInertia = turnSpeed;

}

/*KJL****************************************************************************

************

* The player's AngVelocity as set by the above code is only valid in the player's object *

* space, and so has to be rotated into world space. So aliens can walk on the ceiling, etc. *

***************************************************************************

*************KJL*/

if (dynPtr->AngVelocity.EulerY)

{

MATRIXCH mat;

int angle = MUL_FIXED(NormalFrameTime,dynPtr->AngVelocity.EulerY)&4095;

int cos = GetCos(angle);

int sin = GetSin(angle);

mat.mat11 = cos;

mat.mat12 = 0;

mat.mat13 = -sin;

mat.mat21 = 0;

mat.mat22 = 65536;

mat.mat23 = 0;

mat.mat31 = sin;

mat.mat32 = 0;

mat.mat33 = cos;

MatrixMultiply(&dynPtr->OrientMat,&mat,&dynPtr->OrientMat);

MatrixToEuler(&dynPtr->OrientMat, &dynPtr->OrientEuler);

}<!--c2--></div><!--ec2-->

I can share the files if anyone wants to take a look directly :)

2010-11-20Member:75079MembersThis might help, there is a bunch of methods to transform coordinates :

<a href="http://damien.kodingen.com/ns2docs/classes/Coords.html" target="_blank">http://damien.kodingen.com/ns2docs/classes/Coords.html</a>

Something like that might work :

local angles = Angles(0,0,0)

local coords = angles:GetCoords()

local wallRoll = math.acos(wallNormal.y)

coords = coords.GetRotation(coords.zAxis, wallRoll)

coords = coords.GetRotation(coords.yAxis, input.yaw)

coords = coords.GetRotation(coords.xAxis, input.pitch)

angles = coords:GetAngles()

self:SetViewAngles(angles)

2010-11-20Member:75079Members<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->

local angles = Angles(0,0,0)

local coords = angles:GetCoords()

local wallRoll = -math.acos(self.wallWalkingNormalCurrent.y)

coords = coords*Coords.GetRotation(coords.zAxis, wallRoll)*Coords.GetRotation(coords.yAxis, input.yaw)*Coords.GetRotation(coords.xAxis, input.pitch)

angles:BuildFromCoords(coords)

self:SetViewAngles(angles)<!--c2--></div><!--ec2-->

I'm not sure how Coords.GetRotation(axis, angle) works, but it works like a rotation matrix (specified by an axis and an angle). So the order in which you multiply matter (found out this one by trial and error :).

There need to more stuff in the computation of wallRoll, because now it works only of the wall on you left (you need to change the sign). I failed to find a way to fix it. I'll post the code when more time is available.

<center><object width="450" height="356"><param name="movie" value="http://www.youtube.com/v/HCauC5_WI84"></param><embed src="http://www.youtube.com/v/HCauC5_WI84" type="application/x-shockwave-flash" width="450" height="356"></embed></object></center>

2004-06-19Member:29388Members, Constellation, Squad Five BlueLine 315 of Skulk.lua:

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// Prefer spinning around the x axis.

if self:GetCoords().xAxis:DotProduct(self.wallWalkingNormalGoal) ~= -1 then

normalDiff = self.wallWalkingNormalGoal - self:GetCoords().xAxis

else

normalDiff = self.wallWalkingNormalGoal - self.wallWalkingNormalCurrent:GetPerpendicular()

end<!--c2--></div><!--ec2-->

If you edit that to:

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// Prefer spinning around the x axis.

normalDiff = self.wallWalkingNormalGoal - self.wallWalkingNormalCurrent:GetPerpendicular()<!--c2--></div><!--ec2-->

That might solve the problem.

My brain is tired. Nice video Yukki, I can't get the code to work :P

2007-12-24Member:63250Members[0,1,0] . [0,y,z] = y

[0,1,0] . [x,y,0] = y<!--QuoteEnd--></div><!--QuoteEEnd-->

No, because

N = [x,y,z]; has length = 1

N' = normalised[0,y,z]; has length = 1

N* = normalised[x,y,0]; has length = 1

So if N was [1/sqrt(6), <b>1/sqrt(6)</b>, sqrt(2/3)]

N' = normalised[<u>0</u>, <b>1/sqrt(6)</b>, sqrt(2/3)] = [<u>0</u>, <b>1/sqrt(5)</b>, 2/sqrt(5)]

N* = normalised[1/sqrt(6), <b>1/sqrt(6)</b>, <u>0</u>] = [1/sqrt(2), <b>1/sqrt(2)</b>, <u>0</u>]

After watching that video, I'm really excited about this.

Also, it may be wise to create a cube room, then a cylindrical room, then a spherical room, to test this sort of thing, before we test (and refine) it on these complex geometries.

<!--quoteo(post=1897366:date=Jan 26 2012, 06:33 AM:name=Yuuki)--><div class='quotetop'>QUOTE (Yuuki @ Jan 26 2012, 06:33 AM) <a href="index.php?act=findpost&pid=1897366"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec--><!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->

local angles = Angles(0,0,0)

local coords = angles:GetCoords()

local wallRoll = -math.acos(self.wallWalkingNormalCurrent.y)

coords = coords*Coords.GetRotation(coords.zAxis, wallRoll)*Coords.GetRotation(coords.yAxis, input.yaw)*Coords.GetRotation(coords.xAxis, input.pitch)

angles:BuildFromCoords(coords)

self:SetViewAngles(angles)<!--c2--></div><!--ec2--><!--QuoteEnd--></div><!--QuoteEEnd-->

I'm curious why you only roll and do not pitch with the wall angle.

Also, am I right in assuming that you have omitted this:

<!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec--><!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local viewAngles = Angles(input.pitch, input.yaw, 0)

self:SetViewAngles(viewAngles)<!--c2--></div><!--ec2--><!--QuoteEnd--></div><!--QuoteEEnd-->

Going to try working on your code, won't be able to test it though, will paste it here when I'm done.

Also, question, how do camera angles relate to movement? That could be another source of error. (We only want to change the plane of movement with the surface normal, not tie all movement to the player's camera including their mouse inputs.)

2007-12-24Member:63250Members<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local angles = Angles(0,0,0)

local coords = angles:GetCoords()

local wallNormal = self.wallWalkingNormalCurrent

local wallNormalRoll = wallNormal

wallNormalRoll.z = 0 // z is roll-axis, set z to 0

wallNormalRoll = GetNormalizedVector(wallNormalRoll) // normalise, length = 1

local wallNormalPitch = wallNormal

wallNormalPitch.x = 0 // x is pitch-axis, set x to 0

wallNormalPitch = GetNormalizedVector(wallNormalPitch) // normalise, length = 1

local wallRoll = 0

local wallPitch = 0

if wallNormalRoll.x < 0 // when x is negative

wallRoll = -math.acos(wallNormalRoll.y) // roll is negative

else // when x is positive

wallRoll = math.acos(wallNormalRoll.y) // roll is positive

end

if wallNormalPitch.z < 0 // when z is negative

wallPitch = math.acos(wallNormalPitch.y) // pitch is positive

else // when z is positive

wallPitch = -math.acos(wallNormalPitch.y) // pitch is negative

end

coords = coords*Coords.GetRotation(coords.zAxis, wallRoll)*Coords.GetRotation(coords.xAxis, wallPitch)*Coords.GetRotation(coords.yAxis, input.yaw)*Coords.GetRotation(coords.xAxis, input.pitch)

angles:BuildFromCoords(coords)

self:SetViewAngles(angles)<!--c2--></div><!--ec2-->

roll only:

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local angles = Angles(0,0,0)

local coords = angles:GetCoords()

local wallNormalRoll = self.wallWalkingNormalCurrent

wallNormalRoll.z = 0 // z is roll-axis, set z to 0

wallNormalRoll = GetNormalizedVector(wallNormalRoll) // normalise, length = 1

local wallRoll = 0

if wallNormalRoll.x < 0 // when x is negative

wallRoll = -math.acos(wallNormalRoll.y) // roll is negative

else // when x is positive

wallRoll = math.acos(wallNormalRoll.y) // roll is positive

end

coords = coords*Coords.GetRotation(coords.zAxis, wallRoll)*Coords.GetRotation(coords.yAxis, input.yaw)*Coords.GetRotation(coords.xAxis, input.pitch)

angles:BuildFromCoords(coords)

self:SetViewAngles(angles)<!--c2--></div><!--ec2-->

pitch only:

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local angles = Angles(0,0,0)

local coords = angles:GetCoords()

local wallNormalPitch = self.wallWalkingNormalCurrent

wallNormalPitch.x = 0 // x is pitch-axis, set x to 0

wallNormalPitch = GetNormalizedVector(wallNormalPitch) // normalise, length = 1

local wallPitch = 0

if wallNormalPitch.z < 0 // when z is negative

wallPitch = math.acos(wallNormalPitch.y) // pitch is positive

else // when z is positive

wallPitch = -math.acos(wallNormalPitch.y) // pitch is negative

end

coords = coords*Coords.GetRotation(coords.xAxis, wallPitch)*Coords.GetRotation(coords.yAxis, input.yaw)*Coords.GetRotation(coords.xAxis, input.pitch)

angles:BuildFromCoords(coords)

self:SetViewAngles(angles)<!--c2--></div><!--ec2-->

2010-11-20Member:75079Membersin skulk.lua (as Skulk:UpdateViewAngles(input)) and modify the function according to above codes.

We could maybe do it in Skulk:PlayerCameraCoordsAdjustment(cameraCoords) instead.

Right about the dot product, I missed the normalization.

For the movement you need to override Player:ComputeForwardVelocity(input).

Just need to change :

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local angles = self:ConvertToViewAngles(input.pitch, input.yaw, 0)

local viewCoords = angles:GetCoords()<!--c2--></div><!--ec2-->

To :

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local viewCoords = self:GetCoords()<!--c2--></div><!--ec2-->

>Soul_Rider

I think it's the rotation for the model, could be useful to look at though.

<!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec-->if wallNormalRoll.x < 0 // when x is negative

wallRoll = -math.acos(wallNormalRoll.y) // roll is negative

else // when x is positive

wallRoll = math.acos(wallNormalRoll.y) // roll is positive

end<!--QuoteEnd--></div><!--QuoteEEnd-->

I don't think it's gonna work, wallNormalRoll.x is invariant but when you turn your view around the wall on your left is on your right.

2007-12-24Member:63250MembersCould you just rotate the vector wallNormal to match up with the player's personal x and z axes?

<img src="http://i.imgur.com/2Uzia.png" border="0" class="linked-image" />

<strike>pitch only</strike> <b>roll only</b>

e.g.

local coords = angles:GetCoords()

local wallNormal = self.wallWalkingNormalCurrent

local offset = math.acos(self:GetCoords().z) // angle between personal z axis and absolute z axis

wallNormal = wallNormal*Coords.GetRotation(wallNormal.yAxis, offset) // rotate around y axis

local wallNormalRoll = wallNormal

...

angles:BuildFromCoords(coords)

self:SetViewAngles(angles)<!--c2--></div><!--ec2-->

Not sure if I have the right vector, self:GetCoords(), and not sure if offset should be positive or negative.

Actually, I'm curious why we don't just initialise

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local angles = self:ConvertToViewAngles(input.pitch, input.yaw, 0)<!--c2--></div><!--ec2-->

instead of

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local angles = Angles(0,0,0)<!--c2--></div><!--ec2-->

and then not need to bother doing these rotations:

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->*Coords.GetRotation(coords.yAxis, input.yaw)*Coords.GetRotation(coords.xAxis, input.pitch)<!--c2--></div><!--ec2-->

and only do the necessary rotations for the wall normal.

e.g.

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local angles = self:ConvertToViewAngles(input.pitch, input.yaw, 0)

local coords = angles:GetCoords()

local wallNormal = self.wallWalkingNormalCurrent

local offset = math.acos(self:GetCoords().z) // angle between personal z axis and absolute z axis

wallNormal = wallNormal*Coords.GetRotation(wallNormal.yAxis, offset) // rotate around y axis

local wallNormalRoll = wallNormal

wallNormalRoll.z = 0 // z is roll-axis, set z to 0

wallNormalRoll = GetNormalizedVector(wallNormalRoll) // normalise, length = 1

local wallNormalPitch = wallNormal

wallNormalPitch.x = 0 // x is pitch-axis, set x to 0

wallNormalPitch = GetNormalizedVector(wallNormalPitch) // normalise, length = 1

local wallRoll = 0

local wallPitch = 0

if wallNormalRoll.x < 0 // when x is negative

wallRoll = -math.acos(wallNormalRoll.y) // roll is negative

else // when x is positive

wallRoll = math.acos(wallNormalRoll.y) // roll is positive

end

if wallNormalPitch.z < 0 // when z is negative

wallPitch = math.acos(wallNormalPitch.y) // pitch is positive

else // when z is positive

wallPitch = -math.acos(wallNormalPitch.y) // pitch is negative

end

coords = coords*Coords.GetRotation(coords.zAxis, wallRoll)*Coords.GetRotation(coords.xAxis, wallPitch)

angles:BuildFromCoords(coords)

self:SetViewAngles(angles)<!--c2--></div><!--ec2-->

Well, a lot of this is up in the air. Tell me what you think.

2007-12-26Member:63260Members, Reinforced - ShadowLooks just like what I've been hoping for! Strange thing about the right wall, does it happen if you go on the left wall and aim so that it become your right wall too? At one point in the video it seemed like the roll behaved as if the wall was a ceiling instead of floor, i.e a sign wrong somewhere.

Out of curiosity, why are all the codes following this so much more complex then the initial ones?

2004-06-19Member:29388Members, Constellation, Squad Five BlueOut of curiosity, why are all the codes following this so much more complex then the initial ones?<!--QuoteEnd--></div><!--QuoteEEnd-->

Because we are trying to do something a more complex than the original code allows :) To enable wallwalking the player is rotated around the x-axis. This causes issues when you want to go upside down, and is causing the current issue of of keys being reversed on the walls. This new code is written to solve the key changes, but only works on one wall :)

Good question there Fluid. If you are walking on the left wall, then about face so you are looking behind you, does the view flip so you become attached by the head?

2007-12-26Member:63260Members, Reinforced - ShadowGood question there Fluid. If you are walking on the left wall, then about face so you are looking behind you, does the view flip so you become attached by the head?<!--QuoteEnd--></div><!--QuoteEEnd-->

Well, I mean compared to the code Yuuki added with the left wall working video. I can tell for sure that the inouts work there; it is very hard to walk and look around when your yaw pitches and your pitch yawes. :)

2004-06-19Member:29388Members, Constellation, Squad Five Bluelocal coords = angles:GetCoords()

local wallNormal = self.wallWalkingNormalCurrent

local offset = math.acos(self:GetCoords().z) // angle between personal z axis and absolute z axis

wallNormal = wallNormal*Coords.GetRotation(wallNormal.yAxis, offset) // rotate around y axis

local wallNormalRoll = wallNormal

wallNormalRoll.z = 0 // z is roll-axis, set z to 0

wallNormalRoll = GetNormalizedVector(wallNormalRoll) // normalise, length = 1

local wallNormalPitch = wallNormal

wallNormalPitch.x = 0 // x is pitch-axis, set x to 0

wallNormalPitch = GetNormalizedVector(wallNormalPitch) // normalise, length = 1

local wallRoll = 0

local wallPitch = 0

if wallNormalRoll.x < 0 // when x is negative

wallRoll = -math.acos(wallNormalRoll.y) // roll is negative

else // when x is positive

wallRoll = math.acos(wallNormalRoll.y) // roll is positive

end

if wallNormalPitch.z < 0 // when z is negative

wallPitch = math.acos(wallNormalPitch.y) // pitch is positive

else // when z is positive

wallPitch = -math.acos(wallNormalPitch.y) // pitch is negative

end

coords = coords*Coords.GetRotation(coords.zAxis, wallRoll)*Coords.GetRotation(coords.xAxis, wallPitch)

angles:BuildFromCoords(coords)

self:SetViewAngles(angles)<!--c2--></div><!--ec2-->

Well, a lot of this is up in the air. Tell me what you think.<!--QuoteEnd--></div><!--QuoteEEnd-->

The problem with this code is that

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local offset = math.acos(self:GetCoords().z) // angle between personal z axis and absolute z axis<!--c2--></div><!--ec2-->

Will always return 0. I tried adding an if clause, if self:GetIsWallWalking() then, but that just delayed the problem until I hit the wall. You are trying to work out the degree of rotation before there is any rotation.

The original roll and pitch code needs 'then' added after each of the 2 if statements. This code will run, but it doesn't solve the problems.

Also, it doesn't like going upside down. If you strafe up a curved wall to the left, you hit 90 degrees and stop. The mouse keys have not changed their directional binding at all, so from a 90 degree wall, pressing right is now 'up' ie still world right. Mouse movement is also rotated wrongly.

Taking a step back a moment we have:

A camera orientation

A world orientation

An input orientation

Currently the input orientation is tied to the world, and we need it tied to camera/player orientation.

Would it not be better if we applied the code to Skulk:PlayerCameraCoordsAdjustment(cameraCoords) and made an input rotation based on that? This way we also know the rotations have already happened, as we are now applying the camera adjustments.

Been working on this for hours, turning my brain off now....

2007-12-24Member:63250Members@Fluid Core+: This is the process that I've implemented, and it explains why the new implementation is so much more involved than the original implementation:

- First, you essentially rotate the absolute axes around the y-axis to match up with the player's personal x and z axes (you need a vector that does so: one whose x and z change with the player's orientation, but whose y remains parallel with the absolute y axis; I'm hoping that this is <b>self:getCoords()</b>). This is achieved by taking the inverse cosine of the dot product between the two z-axes i.e. with both being unit vectors: [0,0,1].[x,0,z] = z, conveniently. So <i><b>offset = arccos(z)</b></i>. <a href="http://www.wolframalpha.com/input/?i=arccos%28x%29*180%2Fpi+from+-1+to+1" target="_blank">http://www.wolframalpha.com/input/?i=arcco...pi+from+-1+to+1</a>

- Next, you take the wallNormal with respect to the modified axis above.

-> Actually, rotating the axis and taking the wallnormal with respect to the modified axis would be far too difficult to implement, so you instead do the two aforementioned steps the other way around: take the <b>wallNormal</b> (<b>self.wallWalkingNormalCurrent</b>) with respect to the absolute axes, then rotate it (<b>*Coords.GetRotation(wallNormal.yAxis, offset)</b>) to match the above axis. Now we have a good starting point.

- Next, you essentially flatten the wallNormal [x,y,z] into the x-y plane (for roll, z = 0, z is roll-axis) and the z-y plane (for pitch, x = 0, x is pitch-axis); I've called these <b>wallNormalRoll</b> [x,y,0] and <b>wallNormalPitch</b> [0,y,z] respectively.

- Next, you want to find the angle between absolute "up" [0,1,0] and <b>wallNormalRoll</b>, to determine the roll; and the angle between absolute "up" [0,1,0] and <b>wallNormalPitch</b>, to determine the pitch. This is achieved by taking the inverse cosine of the dot product between two unit vectors. First, you have to normalise <b>wallNormalRoll</b> and <b>wallNormalPitch</b> using <b>GetNormalizedVector()</b>: <b>wallNormalRoll</b> [x',y',0]; <b>wallNormalPitch</b> [0,y*,z*]. Now you can take the dot products: [0,1,0].[x',y',0] = y', conveniently; [0,1,0].[0,y*,z*] = y*, conveniently.

- Because the roll/pitch angles are independent of the horizontal (x-z plane) direction of the wall, you have to add dependencies.

-> If you approach a wall from the left (strafe left into the wall), then <b>wallNormal</b> faces your right, so x is positive, and you want to roll right (positive roll); if you approach a wall from the right (strafe right into the wall), then <b>wallNormal</b> faces your left, so x is negative, and you want to roll left (negative roll). Remember that roll is calculated in the x-y plane, but using y alone. So when x is negative, roll is negative, and when when x is positive, roll is positive.

-> If you approach a wall from the front (walk back into the wall), then <b>wallNormal</b> faces forward, so z is positive, and you want to pitch down (negative pitch); if you approach a wall from behind (walk forward into the wall), then <b>wallNormal</b> faces backward, so z is negative, and you want to pitch up (positive pitch). Remember that pitch is calculated in the z-y plane, but using y alone. So when z is negative, pitch is positive, and when z is positive, pitch is negative.

-> Of course, this is dependent on <u>your</u> personal x and z axes, so you have to account for that, and this is what we already did in the first (or second, really) step.

- So, with the above two steps:

-> <i><b>if x < 0, wallRoll = -arccos(y')</b></i>

-> <i><b>if x >= 0, wallRoll = arccos(y')</b></i>

-> <i><b>if z < 0, wallPitch = arccos(y*)</b></i>

-> <i><b>if z >= 0, wallPitch = -arccos(y*)</b></i>

- Rotate the player's view (<b>coords</b>) around the roll-axis using the roll angle (<b>wallRoll</b>), and rotate the player's view (<b>coords</b>) around the pitch-axis using the pitch angle (<b>wallPitch</b>): <b>coords*Coords.GetRotation(coords.zAxis, wallRoll)*Coords.GetRotation(coords.xAxis, wallPitch)</b>

- Then as a last step, you rotate the player's view around the pitch-axis and the yaw-axis using the <b>input.pitch</b> and <b>input.yaw</b> angles respectively: <b>coords*...*Coords.GetRotation(coords.yAxis, input.yaw)*Coords.GetRotation(coords.xAxis, input.pitch)</b>

-> OR you initialise the player's view with <b>input.pitch</b> and <b>input.yaw</b> <i>before</i> the <u>first</u> step:

<b>local angles = self:ConvertToViewAngles(input.pitch, input.yaw, 0)

local coords = angles:GetCoords()</b> * not sure this will work as intended.

Contrast this with Yuuki's original implementation:

- Take the angle between the <i><u>unflattened</u></i> <b>wallNormal</b> [x,y,z] and absolute "up" [0,1,0], using the inverse cosine of the dot product of both unit vectors: [x,y,z].[0,1,0] = y. Taking the inverse cosine gives the angle.

-> Set this angle as the roll angle, <i><b>wallRoll = arccos(y)</i></b>

- Rotate the player's view (coords) around the roll-axis using the roll angle: <b>coords*Coords.GetRotation(coords.zAxis, wallRoll)</b>

- Then as a last step, you rotate the player's view around the pitch-axis and the yaw-axis using the <b>input.pitch</b> and <b>input.yaw</b> angles respectively: <b>coords*...*Coords.GetRotation(coords.yAxis, input.yaw)*Coords.GetRotation(coords.xAxis, input.pitch)</b>

-> OR you initialise the player's view with <b>input.pitch</b> and <b>input.yaw</b> <i>before</i> the <u>first</u> step:

<b>local angles = self:ConvertToViewAngles(input.pitch, input.yaw, 0)

local coords = angles:GetCoords()</b> * not sure this will work as intended.

<!--quoteo(post=1897566:date=Jan 27 2012, 09:00 AM:name=Soul_Rider)--><div class='quotetop'>QUOTE (Soul_Rider @ Jan 27 2012, 09:00 AM) <a href="index.php?act=findpost&pid=1897566"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->The problem with this code is that

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local offset = math.acos(self:GetCoords().z) // angle between personal z axis and absolute z axis<!--c2--></div><!--ec2-->

Will always return 0.<!--QuoteEnd--></div><!--QuoteEEnd-->

Okay, so self:GetCoords() is the wrong vector. We need a different vector.

This is the criteria:

<!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec-->First, you essentially rotate the absolute axes around the y-axis to match up with the player's personal x and z axes (you need a vector that does so: one whose x and z change with the player's orientation, but whose y remains parallel with the absolute y axis; I'm hoping that this is <b>self:getCoords()</b>).<!--QuoteEnd--></div><!--QuoteEEnd-->

Could you take the world orientation (that is, the orientation of the player in the world), the model orientation, the orientation based on input.pitch and input.yaw, or the state in the previous frame?

2004-06-19Member:29388Members, Constellation, Squad Five Bluens2_skulktest

<img src="http://img.photobucket.com/albums/v260/Soulrefuge/skulktestmap.png" border="0" class="linked-image" />

download here:

<a href="http://www.filehosting.org/file/details/305766/ns2_skulktest.rar" target="_blank">http://www.filehosting.org/file/details/30...2_skulktest.rar</a>

2007-12-26Member:63260Members, Reinforced - ShadowHey! It should probably work to replace the minus sign in the wallroll calculation Yuuki did with cameracoords dot wallnormal. But I'm on my phone so can't test it. But maybe I can sleep now :)

2004-06-19Member:29388Members, Constellation, Squad Five Blue<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->if (dynPtr->AngVelocity.EulerY)

{

MATRIXCH mat;

int angle = MUL_FIXED(NormalFrameTime,dynPtr->AngVelocity.EulerY)&4095;

int cos = GetCos(angle);

int sin = GetSin(angle);

mat.mat11 = cos;

mat.mat12 = 0;

mat.mat13 = -sin;

mat.mat21 = 0;

mat.mat22 = 65536;

mat.mat23 = 0;

mat.mat31 = sin;

mat.mat32 = 0;

mat.mat33 = cos;

MatrixMultiply(&dynPtr->OrientMat,&mat,&dynPtr->OrientMat);

MatrixToEuler(&dynPtr->OrientMat, &dynPtr->OrientEuler);<!--c2--></div><!--ec2-->

Which is as far as I'm aware is the implementation of this

<img src="http://upload.wikimedia.org/wikipedia/en/math/1/c/8/1c89722619b756d05adb4ea38ee6f62b.png" border="0" class="linked-image" />

from

<a href="http://en.wikipedia.org/wiki/3D_projection" target="_blank">http://en.wikipedia.org/wiki/3D_projection</a>

I may be mistaken, my head is literally pounding :P Will look at it all with fresh eyes tomorrow :)

2007-12-24Member:63250MembersIt doesn't work that way.

I'm too tired to explain, so allow me to drop the (incomplete <!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->(I reached a conclusion and put it away)<!--sizec--></span><!--/sizec-->) wall of text and numbers I used to work this out:

Keep in mind that

dot product = [x,y,z]*[0,1,0] = y

<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->Normal Vector Orientation Roll goal Pitch goal

(straight scenarios: pointing towards the face-centres of a cube)

1[ 0, 1, 0] up roll = 0 pitch = 0

2[ 0,-1, 0] ??down roll = 180 pitch = 0 OR roll = 0 pitch = 180 OR roll = 180 pitch = 180

3[ 1, 0, 0] right roll = 90 pitch = 0

4[-1, 0, 0] left roll = -90 pitch = 0

5[ 0, 0, 1] forward roll = 0 pitch = -90

6[ 0, 0,-1] backward roll = 0 pitch = 90

(transitional scenarios 1: pointing towards the edge-centres of a cube)

* b = sqrt(1/2)

1-3[ b, b, 0] up-right roll = 45 pitch = 0

1-4[-b, b, 0] up-left roll = -45 pitch = 0

2-3[ b,-b, 0] down-right roll = 135 pitch = 0

2-4[-b,-b, 0] down-left roll = -135 pitch = 0

1-5[ 0, b, b] up-forward roll = 0 pitch = -45

1-6[ 0, b,-b] up-backward roll = 0 pitch = 45

2-5[ 0,-b, b] down-forward roll = 0 pitch = -135

2-6[ 0,-b,-b] down-backward roll = 0 pitch = 135

3-5[ b, 0, b] ??right-forward roll = 45 pitch = -90 OR roll = 90 pitch = -45

3-6[ b, 0,-b] ??right-backward roll = 45 pitch = 90 OR roll = 90 pitch = 45

4-5[-b, 0, b] ??left-forward roll = -45 pitch = -90 OR roll = -90 pitch = -45

4-6[-b, 0,-b] ??left-backward roll = -45 pitch = 90 OR roll = -90 pitch = 45

(transitional scenarios 2: pointing towards the vertexes of a cube)

* c = sqrt(1/3)

1-3-5[ c, c, c] up-right-forward roll = 45 pitch = -45

1-3-6[ c, c,-c] up-right-backward roll = 45 pitch = 45

1-4-5[-c, c, c] up-left-forward roll = -45 pitch = -45

1-4-6[-c, c,-c] up-left-backward roll = -45 pitch = 45

2-3-5[ c,-c, c] ??down-right-forward roll = 135 pitch = -45 OR roll = 45 pitch = -135

2-3-6[ c,-c,-c] ??down-right-backward roll = 135 pitch = 45 OR roll = 45 pitch = 135

2-4-5[-c,-c, c] ??down-left-forward roll = -135 pitch = -45 OR roll = -45 pitch = -135

2-4-6[-c,-c,-c] ??down-left-backward roll = -135 pitch = 45 OR roll = -45 pitch = 135

1 roll = +-arccos( 1) pitch = +-arccos( 1)

2a roll = ??arccos(-1) pitch = +-arccos( 1) 2b roll = +-arccos( 1) pitch = ??arccos(-1)

3 roll = +arccos( 0) pitch = +-arccos( 1)

4 roll = -arccos( 0) pitch = +-arccos( 1)

5 roll = +-arccos( 1) pitch = -arccos( 0)

6 roll = +-arccos( 1) pitch = +arccos( 0)

1-3 roll = +arccos( b) pitch = +-arccos( 1)

1-4 roll = -arccos( b) pitch = +-arccos( 1)

2-3 roll = +arccos(-b) pitch = +-arccos( 1)

2-4 roll = -arccos(-b) pitch = +-arccos( 1)

1-5 roll = +-arccos( 1) pitch = -arccos( b)

1-6 roll = +-arccos( 1) pitch = +arccos( b)

2-5 roll = +-arccos( 1) pitch = -arccos(-b)

2-6 roll = +-arccos( 1) pitch = +arccos(-b)

3-5a

3-6a

4-5a

4-6a<!--c2--></div><!--ec2-->

Look at 1-3 compared to 1-4, and 2-3 compared to 2-4.

1-3 and 1-4 have the same dot product: b, but you want to roll in different directions.

2-3 and 2-4 have the same dot product: -b, but you want to roll in different directions.

Actually, have a picture:

<a href="http://imageshack.us/photo/my-images/37/4scenariosnot2.png/" target="_blank"><img src="http://img37.imageshack.us/img37/7879/4scenariosnot2.th.png" border="0" class="linked-image" /></a>

Uploaded with <a href="http://imageshack.us" target="_blank">ImageShack.us</a>

Basically: 4 scenarios not 2.

I really have to get going now.

@Soul_Rider, the transformation matrix is already in the game and we've been using it in our code:

Vector*Coords.GetRotation(axis, angle)

Also, nice work on the map :D

2011-10-13Member:127255MembersIt's a Quake3/Unreal Tournament mishmash with a heavy emphasis on movement. Obviously, NS2 should not fully imitate it, but it has very fluid and "movie quality" movement including walljumping and short dashes on the ground.

Here's an <a href="http://www.youtube.com/watch?v=8qFizuMoSbM" target="_blank">example</a>.

I would really love to see a toned down version of that movement in this game someday.

2010-11-20Member:75079MembersThis doesn't work because wallNormal is a vector, it doesn't have axis.

Another way to do it is to build up the coordinate system of the camera from its axis :

coords.yAxis = wallNormal

coords.xAxis = ?

coords.zAxis = ?

<!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec-->Would it not be better if we applied the code to Skulk:PlayerCameraCoordsAdjustment(cameraCoords) and made an input rotation based on that? This way we also know the rotations have already happened, as we are now applying the camera adjustments.<!--QuoteEnd--></div><!--QuoteEEnd-->

I think it's simpler to build up everything from scratch.

Maybe we should continue this into the skulk view rotation topic, and keep this one for more general discussion ?

2004-06-19Member:29388Members, Constellation, Squad Five Blue2007-12-24Member:63250Memberscoords.yAxis = wallNormal

coords.zAxis = f(input.yaw, input.pitch)

coords.xAxis = perpendicular to yAxis and zAxis

?

<a href="http://www.unknownworlds.com/ns2/forums/index.php?showtopic=115601" target="_blank">Very well...</a>