Skulk View Rotation

13

Comments

  • HarimauHarimau Join Date: 2007-12-24 Member: 63250Members
    edited January 2012
    lol... damn it. Why is this so difficult?

    Hmm... I think I see what's happening. Maybe. Let me have a look at the code again and think about it.

    Also, I think we may need to add ComputeForwardVelocity() to override Player:ComputeForwardVelocity(), but I'm not sure. I'm still unsure how movement actually works for the skulk.

    Edit: Order matters...

    Try this:
    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->coords = coords*Coords.GetRotation(coords.yAxis, input.yaw)*Coords.GetRotation(coords.xAxis, input.pitch)<!--c2--></div><!--ec2-->
    Because the x-axis is dependent on the yaw, so it would affect the effect of the pitch.
    I have a feeling that'll only break it the other way, (the yAxis is dependent on the pitch, so it would affect the effect of the yaw) but it's worth trying.

    Ah, no, try this:
    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->coords = coords*Coords.GetRotation(wallNormal, input.yaw)*Coords.GetRotation(coords.xAxis, input.pitch)<!--c2--></div><!--ec2-->
    The x-axis is dependent on the yaw, so it would affect the effect of the pitch.
    The wallnormal is independent of the pitch, so it shouldn't affect the effect of the yaw. This is basically how it's supposed to work:
    - On the ground, you look straight up, you turn right, your view "rolls", that's because it's rotating around the world's y-axis, not your camera's y-axis. In our case, the world's y-axis has to be replaced by the wallnormal. **
    - On the other hand, as you turn around, your x-axis (the pitch axis) moves around, and moving the mouse up and down rotates it around the current x-axis. On the ground, the x-axis is constrained in the horizontal plane. In our case, the x-axis is constrained by the surface.

    ** This was, clearly, something I simply overlooked (why would we rotate around the camera's y-axis? This is not a space flight simulator***).

    Breakdown:
    from reference axes -> rotate around absolute wallNormal -> (yaw) -> set player x-axis -> rotate around player x-axis -> (pitch) -> set player y-axis

    I think the order still matters, because:
    from reference axes -> rotate around * x-axis -> (pitch) -> set player y-axis -> rotate around absolute wallNormal -> (yaw) -> set player x-axis
    * this is, I think, the reference frame's x-axis, so it would attempt to pitch in the wrong direction.


    *** On the note of a flight simulator... order is going to be a ######...
    Edit: Actually, no... Order is only a ###### because we're using rotation matrices... A (crude) implementation of a flight simulator would simply involve:
    local angles = Angles(input.pitch,input.yaw,input.roll)
    self:SetViewAngles(angles)


    Oh, one more thing: With regard to the crazy flipping from one face to another, I think that's mostly dependent on the determination of the wallNormal (different function), I think what's happening is it's grabbing the wallNormal on the opposite side of the geometry rather than the proper side. I'm not sure how to fix that, maybe more shallow <a href="http://www.unknownworlds.com/ns2/forums/index.php?showtopic=115926&view=findpost&p=1896657" target="_blank">wallwalking "feelers"</a>?
    By the way, it may be worthwhile drawing the reference axes and the player axes on the screen for debugging. For some info on how to do this: <a href="http://www.unknownworlds.com/ns2/forums/index.php?showtopic=115926&view=findpost&p=1895214" target="_blank">read the code in the first two pages of this thread</a>
  • YuukiYuuki Join Date: 2010-11-20 Member: 75079Members
    For the movement you just need to override ComputeForwardVelocity and replace

    local angles = self:ConvertToViewAngles(input.pitch, input.yaw, 0)
    local viewCoords = angles:GetCoords()

    by

    local viewCoords = self:GetCoords()

    But the main problem is the mouse/camera right now, not really the movement.

    I think we should look and document a bit more the different function of the game, because I think we miss a few things, for example SetViewAngles add up a base angle (self.baseYaw, the other ones are zero) and also call SetAngles, I don't know what it does.

    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->/**
    * Sets the view angles for the player. Note that setting the yaw of the
    * view will also adjust the player's yaw.
    */
    function CameraHolderMixin:SetViewAngles(viewAngles)

        self.viewYaw   = viewAngles.yaw + self.baseYaw
        self.viewPitch = viewAngles.pitch + self.basePitch
        self.viewRoll  = viewAngles.roll + self.baseRoll

        local angles = Angles(self:GetAngles())
        angles.yaw  = self.viewYaw

        self:SetAngles(angles)

    end<!--c2--></div><!--ec2-->
  • HarimauHarimau Join Date: 2007-12-24 Member: 63250Members
    edited January 2012
    I don't think there's (I hope there isn't) anything left that I have overlooked with the mouse/camera code. Rotating around the wallnormal for yaw, THEN rotating around the x-axis for pitch should fix those issues.



    I have a feeling that self.baseYaw depends on the orientation of the in-game spawn.

    What I don't understand with the code you pasted is that it:
    calculates self.viewYaw, self.viewPitch, self.viewRoll,
    then
    creates angles from self:GetAngles() (current angles)
    then
    overwrites the yaw in those angles with the calculated self.viewYaw
    but <b>does nothing</b> with the calculated self.viewPitch, self.viewRoll
    and then it simply sets the current angles to these angles

    So breaking it down: It takes the current view angles, takes the yaw angle out of the input angles, replaces the yaw in the current view angles, then applies the change.

    Edit: Hmm, just realised that self.viewYaw, self.viewPitch, self.viewRoll are global variables, so the latter two might be used elsewhere, just not in this function.
  • YuukiYuuki Join Date: 2010-11-20 Member: 75079Members
    I don't think they are used much else in the lua code, but my guess is that they are used in the c code for rendering, if I remember correctly you can comment self:SetAngles(angles) without affecting camera movement, so my guess is that theses self.viewYaw are where things happens.
  • YuukiYuuki Join Date: 2010-11-20 Member: 75079Members
    edited January 2012
    The non-zero self.baseYaw is causing troubles, putting it too zero helps !

    Here I first build a coordinate system, rotCoords, by simply putting the yAxis to self.wallWalkingNormalCurrent and getting the others axis in the perpendicular plane :

    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->    
        local rotCoords = Coords()
        rotCoords.yAxis = self.wallWalkingNormalCurrent
        rotCoords.zAxis = rotCoords.yAxis:GetPerpendicular()
        rotCoords.xAxis = rotCoords.zAxis:CrossProduct( rotCoords.yAxis )<!--c2--></div><!--ec2-->

    Then I rotate it around it's own x and y axis :

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

    Almost work ! THere is only one problem, sometimes the zAxis just reverse, and you turn around 90 degree, my guess it that it's due to GetPerpendicular() which can do almost what it wants. Should find a better way to do it.

    In the video the non-rotated coordinate system is on the left, and the rotated one on the right :

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

    <a href="http://www.mediafire.com/?3undbcydfpwo0o7" target="_blank">http://www.mediafire.com/?3undbcydfpwo0o7</a>
  • Fluid CoreFluid Core Join Date: 2007-12-26 Member: 63260Members, Reinforced - Shadow
    edited January 2012
    Ok, not sure if this works, I just tried it on one wallwalking normal so far. It should give a consistency on the vectors directions.

    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->rotCoords.zAxis = self.wallWalkingNormalCurrent:Crossproduct([1,0,0])
    //might be other way around, mean to do [wall normal]x[absolute x-axis]
    rotCoords.xAxis = self.wallWalkingNormalCurrent:Crossproduct([0,0,1])
    //might be other way around, mean to do [wall normal]x[absolute z-axis]<!--c2--></div><!--ec2-->

    Testing with wall normal [1,1,1] we get
    rotCoords.zAxis = [0,1,-1]
    rotCoords.xAxis = [1,-1,0]

    testing [0,1,-1]x[1,-1,0] = [1,1,1] so they are an orthogonal basis.

    Testing with wall normal [0,-1,0] we get
    rotCoords.zAxis = [0,0,1]
    rotCoords.xAxis = [-1,0,0]

    testing [0,0,1]x[-1,0,0] = [0,-1,0] so they are an orthogonal basis. We can also see that they just reverse the x-coordinate which is what we would need for strafing to be in the opposite direction compared to absolute while walking upside down.

    Testing with wall normal [-1,0,0] we get
    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->rotCoords.zAxis = [0,0,0] //bad*
    rotCoords.xAxis = [0,1,0]
    if rotCoords.zAxis = [0,0,0] then
        rotCoords.zAxis=rotCoords.xAxis:Crossproduct(rotCoords.yAxis)
        //may be other way around, want [rot x-axis]x[rot y-axis]
    end
    if rotCoords.xAxis = [0,0,0] then
        rotCoords.xAxis=rotCoords.yAxis:Crossproduct(rotCoords.zAxis)
        //may be other way around, want [rot y-axis]x[rot z-axis]
    end<!--c2--></div><!--ec2-->


    *need to do a test to make the vector for this in another way. This situation can't happen for both axises at the same time, so the one who get 0 can be gained from the cross product of the other two


    I think that should make the axises consistent in their directions. The rot z-axis will be in the z-y absolute plane while the rot x-axis will be in the x-y absolute plane.

    But now sleep. I will try tomorrow.
  • HarimauHarimau Join Date: 2007-12-24 Member: 63250Members
    edited January 2012
    Expanding on Fluid Core's work:

    First of all, note that all vectors must be normalised. Such that normalised[0,0,1] = normalised[0,0,0.5], this allows for the dependencies in 3) onwards.



    1) establishing the order

    [1,0,0]x<b>[0,1,0]</b>=<u>[0,0,1]</u>

    i.e. [absolute x-axis]x<b>[wallnormal]</b>=<u>[reference z-axis]</u>
    or x×<b>Y</b>=<u>Z</u>

    also, <u>[reference x-axis]</u>x<b>[wallnormal]</b>=<u>[reference z-axis]</u>
    or <u>X</u>×<b>Y</b>=<u>Z</u>


    2) establishing the order

    [0,1,0]x<b>[0,0,1]</b>=<u>[1,0,0]</u>

    i.e. <b>[wallnormal]</b>x[absolute z-axis]=<u>[reference x-axis]</u>
    or <u>X</u>=<b>Y</b>×z

    also, <b>[wallnormal]</b>x<u>[reference z-axis]</u>=<u>[reference x-axis]</u>
    or <u>X</u>=<b>Y</b>×<u>Z</u>


    3) dependencies

    if <b>[wallnormal]</b>=[absolute x-axis] or <b>[wallnormal]</b>=-[absolute x-axis]
    calculated z-axis becomes [0,0,0]

    actually,
    if <b>[wallnormal]</b>.x != 0
    calculated x-axis becomes [0,-<b>[wallnormal]</b>.z,<b>[wallnormal]</b>.y]
    and length <=1

    so find x-axis first
    <u>X</u>=<b>Y</b>×z
    then
    <u>X</u>×<b>Y</b>=<u>Z</u>


    4) dependencies

    if <b>[wallnormal]</b>=[absolute z-axis] or <b>[wallnormal]</b>=-[absolute z-axis]
    calculated x-axis becomes [0,0,0]

    actually,
    if <b>[wallnormal]</b>.z != 0
    calculated x-axis becomes [<b>[wallnormal]</b>.y,-<b>[wallnormal]</b>.x,0]
    and length <=1

    so find z-axis first
    x×<b>Y</b>=<u>Z</u>
    then
    <u>X</u>=<b>Y</b>×<u>Z</u>


    Summary)
    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->    [reference y-axis] = [wallnormal]
    // condition 1
    if [wallnormal].x != 0 then
        [reference x-axis] = [wallnormal]x[absolute z-axis]
        [reference z-axis] = [reference x-axis]x[wallnormal]
    // condition 2
    elseif [wallnormal].z !=0 then
        [reference z-axis] = [absolute x-axis]x[wallnormal]
        [reference x-axis] = [wallnormal]x[reference z-axis]
    // condition 3
    else then
        [reference x-axis] = [wallnormal]x[absolute z-axis]
        [reference z-axis] = [absolute x-axis]x[wallnormal]

    // actually condition 3 may be compatible with condition 1 or 2, so you may be able to combine conditions 1 and 3 or combine conditions 2 and 3

    // condition 2&3
    if [wallnormal].x = 0 then // (includes if [wallnormal].z !=0)
        [reference z-axis] = [absolute x-axis]x[wallnormal]
        [reference x-axis] = [wallnormal]x[reference z-axis]
    // condition 1
    else then // (if [wallnormal].x != 0)
        [reference x-axis] = [wallnormal]x[absolute z-axis]
        [reference z-axis] = [reference x-axis]x[wallnormal]<!--c2--></div><!--ec2-->


    Example 1) wallnormal UP

    [wallnormal] = {0,1,0} up
    [wallnormal].x = 0, so:
    first, [absolute x-axis] × [wallnormal] = [reference z-axis]
    {1,0,0} × {0,1,0} = {0,0,1} forward
    then, [wallnormal] × [reference z-axis] = [reference x-axis]
    {0,1,0} × {0,0,1} = {1,0,0} right
    up is UP, right is RIGHT, forward is FORWARD (player has not tilted forward or backward or rolled left or right)

    Example 2) wallnormal DOWN

    [wallnormal] = {0,-1,0} up
    [wallnormal].x = 0, so:
    first, [absolute x-axis] × [wallnormal] = [reference z-axis]
    {1,0,0} × {0,-1,0} = {0,0,-1} forward
    then, [wallnormal] × [reference z-axis] = [reference x-axis]
    {0,-1,0} × {0,0,-1} = {1,0,0} right
    up is DOWN, right is RIGHT, forward is BACKWARD (player has tilted forward/backward 180 degrees; note that this doesn't consider rolling left/right 180 degrees, i.e. it prioritises pitch over roll; this could become a problem)

    ...

    Example 3) wallnormal FORWARD

    [wallnormal] = {0,0,1} up
    [wallnormal].z != 0, so:
    first, [absolute x-axis] × [wallnormal] = [reference z-axis]
    {1,0,0} × {0,0,1} = {0,-1,0} forward
    then, [wallnormal] × [reference z-axis] = [reference x-axis]
    {0,0,1} × {0,-1,0} = {1,0,0} right
    up is FORWARD, right is RIGHT, forward is DOWN (player has tilted forward 90 degrees)

    Example 4) wallnormal BACKWARD

    [wallnormal] = {0,0,-1} up
    [wallnormal].z != 0, so:
    first, [absolute x-axis] × [wallnormal] = [reference z-axis]
    {1,0,0} × {0,0,1} = {0,1,0} forward
    then, [wallnormal] × [reference z-axis] = [reference x-axis]
    {0,0,1} × {0,-1,0} = {1,0,0} right
    up is BACKWARD, right is RIGHT, forward is UP (player has tilted backward 90 degrees)

    ...

    Example 5) wallnormal UP-FORWARD

    [wallnormal] = {0,sqrt(1/2),sqrt(1/2)}
    [wallnormal].z != 0, so:
    first, [absolute x-axis] × [wallnormal] = [reference z-axis]
    {1,0,0} × {0,sqrt(1/2),sqrt(1/2)} = {0,-sqrt(1/2),sqrt(1/2)}
    then, [wallnormal] × [reference z-axis] = [reference x-axis]
    {0,sqrt(1/2),sqrt(1/2)} × {0,-sqrt(1/2),sqrt(1/2)} = {1,0,0}
    up is UP-FORWARD, right is RIGHT, forward is FORWARD-DOWN (player has tilted forward 45 degrees)

    ...

    Example 6) wallnormal RIGHT
    [wallnormal] = {1,0,0} up
    [wallnormal].x != 0, so:
    first, [wallnormal] × [absolute z-axis] = [reference x-axis]
    {1,0,0} × {0,0,1} = {0,-1,0} right
    then, [reference x-axis] × [wallnormal] = [reference z-axis]
    {0,-1,0} × {1,0,0} = {0,0,1} forward
    up is RIGHT, right is DOWN, forward is FORWARD (player has rolled right 90 degrees)

    Example 7) wallnormal LEFT
    [wallnormal] = {-1,0,0} up
    [wallnormal].x != 0, so:
    first, [wallnormal] × [absolute z-axis] = [reference x-axis]
    {-1,0,0} × {0,0,1} = {0,1,0} right
    then, [reference x-axis] × [wallnormal] = [reference z-axis]
    {0,1,0} × {-1,0,0} = {0,0,1} right
    up is LEFT, right is UP, forward is FORWARD (player has rolled left 90 degrees)

    ...

    Example 8) wallnormal UP-RIGHT

    [wallnormal] = {sqrt(1/2),sqrt(1/2),0}
    [wallnormal].x != 0, so:
    first, [wallnormal] × [absolute z-axis] = [reference x-axis]
    {sqrt(1/2),sqrt(1/2),0} × {0,0,1} = {sqrt(1/2),-sqrt(1/2),0} right
    then, [reference x-axis] × [wallnormal] = [reference z-axis]
    {sqrt(1/2),-sqrt(1/2),0} × {sqrt(1/2),sqrt(1/2),0} = {0,0,1} forward
    up is UP-RIGHT, right is RIGHT-DOWN, forward is FORWARD (player has rolled right 45 degrees)

    ...

    Example 9a) wallnormal FORWARD-RIGHT***
    [wallnormal] = {sqrt(1/2),0,sqrt(1/2)} up
    [wallnormal].z != 0, so:
    first, [absolute x-axis] × [wallnormal] = [reference z-axis]
    {1,0,0} × {sqrt(1/2),0,sqrt(1/2)} = {0,-sqrt(1/2),0} forward (<b>broken</b>: length < 1)
    then, [wallnormal] × [reference z-axis] = [reference x-axis]
    {sqrt(1/2),0,sqrt(1/2)} × {0,-sqrt(1/2),0} = {1/2,0,-1/2} right (<b>broken</b>: length < 1)
    up is FORWARD-RIGHT, right is RIGHT-BACKWARD, forward is DOWN

    Example 9b) wallnormal FORWARD-RIGHT***
    [wallnormal] = {sqrt(1/2),0,sqrt(1/2)} up
    [wallnormal].x != 0, so:
    first, [wallnormal] × [absolute z-axis] = [reference x-axis]
    {sqrt(1/2),0,sqrt(1/2)} × {0,0,1} = {0,-sqrt(1/2),0} right (<b>broken</b>: length < 1)
    then, [reference x-axis] × [wallnormal] = [reference z-axis]
    {0,-sqrt(1/2),0} × {sqrt(1/2),0,sqrt(1/2)} = {-1/2,0,1/2} forward (<b>broken</b>: length < 1)
    up is FORWARD-RIGHT, right is DOWN, forward is LEFT-FORWARD

    Example 9c) wallnormal FORWARD-RIGHT***
    [wallnormal] = {sqrt(1/2),0,sqrt(1/2)} up
    [wallnormal].x != 0, and [wallnormal].z != 0, so: so:
    first, [wallnormal] × [absolute z-axis] = [reference x-axis]
    {sqrt(1/2),0,sqrt(1/2)} × {0,0,1} = {0,-sqrt(1/2),0} right (<b>broken</b>: length < 1)
    also, [absolute x-axis] × [wallnormal] = [reference z-axis]
    {1,0,0} × {sqrt(1/2),0,sqrt(1/2)} = {0,-sqrt(1/2),0} forward (<b>broken</b>: length < 1)
    up is FORWARD-RIGHT, right is DOWN, forward is DOWN

    compare to FORWARD:
    up is FORWARD, right is RIGHT, forward is DOWN (player has tilted forward 90 degrees)

    compare to RIGHT:
    up is RIGHT, right is DOWN, forward is FORWARD (player has rolled right 90 degrees)

    compare to goal:
    (pitch then roll) up is FORWARD-RIGHT, right is RIGHT-BACKWARD, forward is DOWN
    or
    (roll then pitch) up is FORWARD-RIGHT, right is DOWN, forward is LEFT-FORWARD

    9a and 9b work but have lengths < 1, and which one do you choose?

    ...

    Example 10*) wallnormal UP-FORWARD-RIGHT***

    Even more broken. Not even going to try.



    *** Ugh... All broken. The worst thing is that this corresponds to every configuration of wallnormal not within with the absolute x-y and z-y planes.

    The problem is that you can have pitch (wallnormal in z-y plane), you can have roll (wallnormal in x-y plane), but then when you try to do pitch AND roll, your orientation depends on the order you do it in.
  • YuukiYuuki Join Date: 2010-11-20 Member: 75079Members
    edited January 2012
    I was think about something like that,

    First build the default coordinate system :

    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->  
        local viewAngles = Angles(input.pitch, input.yaw, 0)
        local defaultCoords = viewAngles:GetCoords()<!--c2--></div><!--ec2-->

    then take the zaxis and remove the component parallel to the wall normal :

    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->   rotCoords.yAxis = self.wallWalkingNormalCurrent
       rotCoords.zAxis = defaultCoords.zAxis - rotCoords.yAxis * (defaultCoords.zAxis:DotProduct(rotCoords.yAxis))<!--c2--></div><!--ec2-->

    Should work no ?

    Well you can probably get into troubles when you're facing directly the wall normal in the defaultCoords (DotProduct = +/- 1), but some if statements could solve that.

    Edit: not sure anymore, I should remove yaw rotation then probably.
  • Fluid CoreFluid Core Join Date: 2007-12-26 Member: 63260Members, Reinforced - Shadow
    edited January 2012
    <!--quoteo(post=1898322:date=Jan 31 2012, 07:52 AM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Jan 31 2012, 07:52 AM) <a href="index.php?act=findpost&pid=1898322"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Example 1) wallnormal UP

    [wallnormal] = {0,1,0} up
    [wallnormal].x = 0, so:
    first, [absolute x-axis] × [wallnormal] = [reference z-axis]
    {1,0,0} × {0,1,0} = {0,0,1} forward
    then, [wallnormal] × [reference z-axis] = [reference x-axis]
    {0,1,0} × {0,0,1} = {1,0,0} right
    up is UP, right is RIGHT, forward is FORWARD (player has not tilted forward or backward or rolled left or right)<!--QuoteEnd--></div><!--QuoteEEnd-->

    I'm going to stop here and ask what you mean. I mean I do get what you are doing, but as far as I know, the x-axis points to the left for the player, i.e. if input.move.x is positive then you move to your left. Granted, that may be what you mean, or you express direction in another way then I do, but for me, [1,0,0] would mean that the vector points to the left. Not sure if there is any use for me to go through the rest of the post, as I'll likely get it wrong due to this reason. I'll take a look at the conclusions though.

    <!--quoteo(post=1898322:date=Jan 31 2012, 07:52 AM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Jan 31 2012, 07:52 AM) <a href="index.php?act=findpost&pid=1898322"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->*** Ugh... All broken. The worst thing is that this corresponds to every configuration of wallnormal not within with the absolute x-y and z-y planes.

    The problem is that you can have pitch (wallnormal in z-y plane), you can have roll (wallnormal in x-y plane), but then when you try to do pitch AND roll, your orientation depends on the order you do it in.<!--QuoteEnd--></div><!--QuoteEEnd-->

    No, it's not broken. But when you take the cross product, you may not always perserve length of vectors. You get orthogonal vectors, but not always ortho-normalized vectors. You just have to normalize them after the cross product. It seems like any of the wall normals where more then one component was located in the x-z plane had this property. I'm sure we could prove that, mathematically, but why bother more then needed? Just make the normalization after you have calculated the directions and all should be fine.

    <!--quoteo(post=1898322:date=Jan 31 2012, 07:52 AM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Jan 31 2012, 07:52 AM) <a href="index.php?act=findpost&pid=1898322"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Example 9a) wallnormal FORWARD-RIGHT***

    compare to FORWARD:
    up is FORWARD, right is RIGHT, forward is DOWN (player has tilted forward 90 degrees)

    compare to RIGHT:
    up is RIGHT, right is DOWN, forward is FORWARD (player has rolled right 90 degrees)

    compare to goal:
    (pitch then roll) up is FORWARD-RIGHT, right is RIGHT-BACKWARD, forward is DOWN
    or
    (roll then pitch) up is FORWARD-RIGHT, right is DOWN, forward is LEFT-FORWARD<!--QuoteEnd--></div><!--QuoteEEnd-->

    But something else must be wrong. Let's look at this practically.

    We stand upright on the ground. At our feet we put down a stick, pointing diagonally forward to our right. Now imagine that instead of the stick, we were standing by a cliff where the face was in the same direction as the stick previously, so the cliff will stretch from diagonally forward on our left to diagonally backward on our right. To keep any suicide out of the picture, imagine that we are about to repel down the cliff, face first of course. But since we don't want to swing into the cliff when we go over the edge, we got to orientate ourself to it first. So first we rotate 45 degree clockwise (to the right). We now stand with the cliff stretching from our left to our right. We now start to tilt ourself forward, the rope supporting us, until we are standing with our feet on the cliff, looking straight down. Now we can start to repel down the cliff with no trouble.

    Summary: To change the coordinate system from the normal one, we rotated 45 degree clockwise and pitched 90 degree forward.

    So something else must be wrong, but it's not that they doesn't keep the unit length of 1. And my way wouldn't have worked. Depending on order you cross them, you would get the new x and z axis parallel or anti-parallel.
  • HarimauHarimau Join Date: 2007-12-24 Member: 63250Members
    edited February 2012
    The x-axis is right, the y-axis is up, the z-axis is forward. Sometimes engines swap the y and z axes, or set the z-axis as backward, but the x-axis is always right. Consider a 2-D graph, y vs x.

    The capital letters represent the orientation with respect to the absolute frame of reference, the small letters represent your personal orientation. If you are in a roughly cubic room, you should be able to easily imagine it.

    Look at it this way. You place a stick into the ground. You pitch it forward 45 degrees (so it is now top-forward), then you roll 90 degrees clockwise - which way is the stick pointing? To the forward-right. The exact same way as if you pitched 90 degrees forward (so it is now forward) then roll 45 degrees clockwise.

    I'll show you using vectors for the y-axis:

    Approach 1)
    [0,1,0]
    -> pitch forward 90 degrees (z-axis is now [0,-1,0])
    [0,0,1]
    -> roll right 45 degrees
    [sqrt(1/2),0,sqrt(1/2)]

    Approach 2)
    [0,1,0]
    -> pitch forward 45 degrees (z-axis is now [0,-sqrt(1/2),sqrt(1/2)])
    [0,sqrt(1/2),sqrt(1/2)]
    -> roll right 90 degrees
    [sqrt(1/2),0,sqrt(1/2)]

    Or even,

    Approach 3)
    [0,1,0]
    -> roll right 90 degrees (x-axis is now [0,-1,0])
    [1,0,0]
    -> pitch forward 45 degrees
    [sqrt(1/2),0,sqrt(1/2)]

    Approach 4)
    [0,1,0]
    -> roll right 45 degrees (x-axis is now [sqrt(1/2),-sqrt(1/2),0])
    [sqrt(1/2),sqrt(1/2),0]
    -> pitch forward 90 degrees
    [sqrt(1/2),0,sqrt(1/2)]

    4 different ways to get the same y-axis. But each way may yield a different x and z-axis.

    When you do the cross product with the absolute axis and then the reference axis, the order (naturally) matters: for the exact same reason order matters when using euler angles - they're related. The length of the cross product between vectors isn't always 1, but when the starting vectors for axes is 1 and -there are no other possible configurations- it will be. The fact that the length is less than 1 isn't the problem, but a <b>symptom</b> of the problem: the problem which is, order matters, therefore there are different possible configurations of the other axes when you move one axis. When only pitching, there is no other possible configuration (y-axis and z-axis will change, x-axis will stay the same), that's why the length is always 1. When only rolling, there is no other possible configuration (x-axis and y-axis will change, z-axis will stay the same), that's why the length is always 1.
  • Fluid CoreFluid Core Join Date: 2007-12-26 Member: 63260Members, Reinforced - Shadow
    edited February 2012
    <!--quoteo(post=1898547:date=Feb 1 2012, 09:17 AM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Feb 1 2012, 09:17 AM) <a href="index.php?act=findpost&pid=1898547"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->The x-axis is right, the y-axis is up, the z-axis is forward. Sometimes engines swap the y and z axes, or set the z-axis as backward, but the x-axis is always right. Consider a 2-D graph, y vs x.<!--QuoteEnd--></div><!--QuoteEEnd-->

    Why do you keep doing this? In the game, <b>x-axis is left</b>, y-axis is up and z-axis is forward. You're coordinate system isn't even a standard right-handed system, but a left-handed one.

    <!--quoteo(post=1898547:date=Feb 1 2012, 09:17 AM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Feb 1 2012, 09:17 AM) <a href="index.php?act=findpost&pid=1898547"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->The fact that the length is less than 1 isn't the problem, but a symptom of the problem: the problem which is, order matters, therefore there are different possible configurations of the other axes when you move one axis. When only pitching, there is no other possible configuration (y-axis and z-axis will change, x-axis will stay the same), that's why the length is always 1. When only rolling, there is no other possible configuration (x-axis and y-axis will change, z-axis will stay the same), that's why the length is always 1.<!--QuoteEnd--></div><!--QuoteEEnd-->

    So, why aren't we rotating first around one axis, for example the x, then around the next axis, the z, to get the y-axis to be in the same direction as the wall normal? Isn't that the standard approach to get the new rotated coordinate system? I can't recall how you do the transformation mathematically when you know what new vector you want however.
  • HarimauHarimau Join Date: 2007-12-24 Member: 63250Members
    edited February 2012
    Sorry, I didn't know that.

    ---

    I've been having some new thoughts as to how to get around the order problem, and actually this idea stemmed from my thoughts about how to get the z-axis compensation working. I've just realised we can apply it to the wallnormal/y-axis problem.

    Every axis is expressed as a unit vector with x, y and z components describing the distance from an origin, i.e. vectors are arrows where the end point of an axis describes a position in 3D space, with respect to an origin (the starting point).
    Naturally, this means that every axis is a point on a sphere of radius 1, with the origin at the centre.
    If you shift an axis by rotation, you are really just moving its end point from one point in the sphere to another.
    Considering the axes as points, you can determine the direction vector between points like so:
    Vector1->2 = Point2 - Point1
    or
    Direction = AxisNew - AxisOld
    e.g. yAxisOld = [0,1,0]; yAxisNew = [1,0,0]. Direction = [1,0,0]-[0,1,0] = [1-0,0-1,0-0] = [1,-1,0]
    Now, consider that the origin[0,0,0], yAxisOld, yAxisNew and Direction all exist on a single plane: three points and a vector exist on this plane.
    What we want to do is take a vector (RotationAxis) perpendicular to the vector Direction and the aforementioned plane (we have three points and a vector).
    We find the plane's normal (RotationAxis) using the cross product of (yAxisOld-origin) and (yAxisNew-origin). <i>Reference: <a href="http://jtaylor1142001.net/calcjat/Solutions/VPlanes/VP3Pts.htm" target="_blank">http://jtaylor1142001.net/calcjat/Solution...anes/VP3Pts.htm</a></i>
    When we "move" that vector (RotationAxis) onto the origin, it becomes the axis that we want to rotate the player's axes around.
    Depending on whether the engine does clockwise or counter-clockwise rotations, we want the perpendicular vector to be right (e.g. [0,0,1]) or left (e.g. [0,0,-1]) of the Direction vector, respectively.
    We take the dot product between the old axis and the new axis to calculate the size of the angle (e.g. 90 degrees), then we rotate the reference frame (x-,y- and z-axes) around the RotationAxis for this angle.

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

    So, in summary:
    0 - Get the player's angles.
    1 - Find the plane's normal, this is the cross product: Vector[AxisNew] x Vector[AxisOld] = RotationAxis
    2 - Find the angle between the two vectors, this is the inverse cosine of the dot product: arccos(Vector[AxisNew] . Vector[AxisOld])
    3 - Rotate the player's axes around the RotationAxis for this angle.
    4 - Set the camera.

    How to express AxisNew and AxisOld? We could either express it as the player's previous Axis (e.g. self yAxis), and some desired Axis (e.g. WallWalkingNormal); or we could express it as the world's Axis (e.g. world y-Axis) and some desired Axis (WallWalkingNormal) - this just depends on the approach we want to take: modify state based on previous state or update state based on current conditions? The code below uses the latter approach (the approach we've been using the whole time).

    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->    // 0
        local viewAngles = Angles(input.pitch, input.yaw, 0)
        local defaultCoords = angles:GetCoords()
        // 1
        local rotationAxis = self.wallWalkingNormalCurrent:CrossProduct(Vector(0,1,0)) // ***
        // 2
        local rotationAngle = math.acos(self.wallWalkingNormalCurrent.y)
        // 3
        local rotCoords = Coords()
        rotCoords = defaultCoords*coords.GetRotation(rotationAxis,rotationAngle)
        // 4
        viewAngles:BuildFromCoords(rotCoords)
        self:SetViewAngles(viewAngles) // check that this function does what we want it to<!--c2--></div><!--ec2-->

    (This can probably be shaved down a line or two by just taking coords, instead of defaultCoords and rotCoords separately.)

    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->    // 0
        local angles = Angles(input.pitch, input.yaw, 0)
        local coords = viewAngles:GetCoords()
        // 1
        local rotationAxis = self.wallWalkingNormalCurrent:CrossProduct(Vector(0,1,0)) // ***
        // 2
        local rotationAngle = math.acos(self.wallWalkingNormalCurrent.y)
        // 3
        coords = coords*coords.GetRotation(rotationAxis,rotationAngle)
        // 4
        angles:BuildFromCoords(coords)
        self:SetViewAngles(angles) // check that this function does what we want it to<!--c2--></div><!--ec2-->
    *** You have to check which goes first, but I'm fairly certain this is correct.

    *** Note that there are exactly two cases where this approach will not work: when yAxisOld = +- yAxisNew, i.e. they are parallel (the same) or anti-parallel (the opposite). This is because the three points (yAxisOld, origin, yAxisNew) are colinear, so you cannot create a triangle, so how do you find the plane, and therefore how do you find the RotationAxis? So we have to add a couple dependencies here, i.e. if dot product is 1 or dot product is -1 (or cross product = [0,0,0]), do something else, perhaps simply set some rotationAxis = [x,0,z]; if it's [1,0,0] it'll pitch back 180 degrees (upside down retaining lateral direction), if it's [0,0,1] it'll roll right 180 degrees (upside down retaining forward direction), if it's something like [sqrt(1/2),0,sqrt(1/2)] it'll both pitch and roll in equal measure.

    The good thing about this approach is that it's axes-agnostic. So now you don't pitch (rotate around the x-axis), and roll (rotate around the z-axis), you just rotate once around the RotationAxis with some angle found using the dot product.
  • HarimauHarimau Join Date: 2007-12-24 Member: 63250Members
    edited February 2012
    To get the zAxis compensation, you put the following code before (just in case) the code in my previous post:
    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->    local storeCoords = self:GetCoords()<!--c2--></div><!--ec2-->

    Then the following code before the last two lines in the code in my previous post:
    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->    local compensationAxis = storeCoords.zAxis:CrossProduct(coords.zAxis)
        local dot = storeCoords.zAxis:CrossProduct(coords.zAxis)
        local compensationAngle = math.acos(dot)
        coords = coords*coords.GetRotation(compensationAxis,compensationAngle)<!--c2--></div><!--ec2-->

    To deal with parallel and anti-parallel vectors:
    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->    if rotationAxis = Vector(0,0,0)
            rotationAxis = coords.xAxis // just rotate around player's base x-axis, should be looked at
        end<!--c2--></div><!--ec2-->




    So, altogether now:

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

        local angles = Angles(input.pitch, input.yaw, 0)
        local coords = angles:GetCoords()

        local rotationAxis = self.wallWalkingNormalCurrent:CrossProduct(Vector(0,1,0))
        if rotationAxis = Vector(0,0,0)
            rotationAxis = coords.xAxis // just rotate around player's base x-axis, should be looked at
        end

        local rotationAngle = math.acos(self.wallWalkingNormalCurrent.y)

        coords = coords*coords.GetRotation(rotationAxis,rotationAngle)

        // retain z-axis
        if self:GetIsViewLocked() // made this up: true or false - depends on if the player has the "lock view" option on
            local compensationAxis = storeCoords.zAxis:CrossProduct(coords.zAxis)
            if compensationAxis = Vector(0,0,0)
                compensationAxis = storeCoords.xAxis // just rotate around player's current x-axis, should be looked at
            end

            local dot = storeCoords.zAxis:CrossProduct(coords.zAxis)

            local compensationAngle = math.acos(dot)

            coords = coords*coords.GetRotation(compensationAxis,compensationAngle)
        end

        angles:BuildFromCoords(coords)
        self:SetViewAngles(angles) // check that this function does what we want it to<!--c2--></div><!--ec2-->

    With GetIsViewLocked(), you'll have to build the function yourself, but for now:
    Just comment it out if you don't want compensation, and keep the inside code (remove the if / end) if you do want compensation.
  • YuukiYuuki Join Date: 2010-11-20 Member: 75079Members
    <!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec-->local rotationAxis = self.wallWalkingNormalCurrent:CrossProduct(Vector(0,1,0))<!--QuoteEnd--></div><!--QuoteEEnd-->

    I tried something similar (video above), but I always got into troubles when self.wallWalkingNormalCurrent is parallel to Vector(0,1,0). I tried the if approach :

    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->if math.abs( self.wallWalkingNormalCurrent:DotProduct(Vector(0,1,0)) > 0.5 ) then
    do something
    else
      do something else
    end<!--c2--></div><!--ec2-->

    But it's really hard to get the transition right (going from do to else shouldn't change your view at all), you often get 180 degree rotation, or a sign problem.
    I'll try your solution if I have time.
  • YuukiYuuki Join Date: 2010-11-20 Member: 75079Members
    edited February 2012
    Did a little matlab version of this (it's much easier to work with), you give a yaw and a pitch for the wall normal and it builds a coordinate system which y axis (well z in my matlab implementation, z is vertical) is aligned to the wall normal vector and seems to change continuously when you change the wall normal.

    There is a few problems with the signs and reference angles (like the -pi/2, I have no clue why I have to add this...) but it should be ok, I will try to transpose it in game.

    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->pitch =  0.5;  %between 0 (up) and pi (down)
    yaw   = 0.1;   %any values

    wallNormal = zeros(1,3);

    wallNormal(1) =  sin(pitch) * cos(yaw);
    wallNormal(2) =  sin(pitch) * sin(yaw);
    wallNormal(3) =  cos(pitch);

    wallNormal =  wallNormal / norm(wallNormal);

    clf
    hold on
    plot3([0 wallNormal(1)],[0 wallNormal(2)],[0 wallNormal(3)])

    %plot absolute coordinate system
    plot3([0 1],[0 0],[0 0],'k')
    plot3([0 0],[0 1],[0 0],'k')
    plot3([0 0],[0 0],[0 1],'k')


    % inverse
    yaw = atan2(wallNormal(2),wallNormal(1));
    pitch = acos(wallNormal(3));

    %check what it works
    wallNormal(1) =  sin(pitch) * cos(yaw);
    wallNormal(2) =  sin(pitch) * sin(yaw);
    wallNormal(3) =  cos(pitch);

    wallNormal =  wallNormal / norm(wallNormal);
    plot3([0 wallNormal(1)],[0 wallNormal(2)],[0 wallNormal(3)],'r.')


    %build up rotated coordinate system
    x = [1 0 0]';
    y = [0 1 0]';
    z = [0 0 1]';

    %pitch
    th = -pitch;
    Rx = [1 0 0; 0 cos(th) -sin(th); 0 sin(th) cos(th)];

    x = Rx*x; y = Rx*y; z = Rx*z;

    %yaw
    th = yaw-pi/2;
    Rz = [cos(th) -sin(th) 0; sin(th) cos(th) 0; 0 0 1];

    x = Rz*x; y = Rz*y; z = Rz*z;

    x=0.5*x; y=0.5*y; z=0.5*z;

    plot3([0 x(1)],[0 x(2)],[0 x(3)],'r','lineWidth',2)
    plot3([0 y(1)],[0 y(2)],[0 y(3)],'g','lineWidth',2)
    plot3([0 z(1)],[0 z(2)],[0 z(3)],'b','lineWidth',2)

    xlabel('x')
    ylabel('y')
    zlabel('z')

    view(60,30)
    axis([-1 1 -1 1 -1 1]/1.5)
    axis vis3d<!--c2--></div><!--ec2-->
  • YuukiYuuki Join Date: 2010-11-20 Member: 75079Members
    Hum, did a careful implementation of the above, it works great on the walls, but there is problem with the transitions from ground/ceiling to walls. The ground normal is vertical so the yaw is just given by the last wall you were on (because of the smoothing, it remembers for very long time) and then you get big yaw difference when changing wall...

    Seems hopeless to me, it seems there is some kind of fundamental problem.
  • HarimauHarimau Join Date: 2007-12-24 Member: 63250Members
    edited February 2012
    <!--QuoteBegin-Yuuki+--><div class='quotetop'>QUOTE (Yuuki)</div><div class='quotemain'><!--QuoteEBegin-->I tried something similar (video above), but I always got into troubles when self.wallWalkingNormalCurrent is parallel to Vector(0,1,0).<!--QuoteEnd--></div><!--QuoteEEnd-->
    Which video? Which code approach?

    When the wallnormal is (0,1,0) it shouldn't rotate anyway, because the dot product is 1, so the angle is zero. So it doesn't matter what rotationAxis we set in that case.
    It's really just the case of the (0,-1,0) wallnormal (ceilings) we have to look at. The problem with determining the rotationAxis is that there is no plane to determine the normal from: (0,1,0),(0,0,0),(0,-1,0) are colinear. If you are strafing directly into a ceiling from a wall (or indeed from ground to wall), you want your personal rotation to be around your personal z-axis (or negative z-axis). If you are walking forward directly into a ceiling from a wall (or indeed from ground to wall), you want your personal rotation to be around your personal x-axis (or negative x-axis). How do you express this concept in terms of the absolute coordinate system?**

    Let's look at some cases where the ceiling is almost [0,-1,0] (not normalised):
    [0,-99,1] : rotationAxis = (-1,0,0)
    [1,-99,0] : rotationAxis = (0,0,1)
    [0,-99,-1] : rotationAxis = (1,0,0)
    [-1,-99,0] : rotationAxis = (0,0,-1)
    Basically it's largely dependent on the angle of approach. The player's starting axes (input.pitch,input.yaw) are irrelevant in this case, because that's what we're rotating.

    It may solve (or at least sidestep) all our issues if we modify the state based on the previous state, rather than updating the state.
    So, for instance:
    1. Take the player's current view
    2. Find the rotationAxis between the last frame's wallNormal and the current frame's wallNormal (rotationAxis = wallNormalCurrent x wallNormalPrevious)
    3. Find the rotationAngle between the last frame's wallNormal and the current frame's wallNormal (angle = arccos(wallNormalCurrent . wallNormalPrevious))
    4. Rotate the player's current view around rotationAxis for rotationAngle

    The only time the system would break is if a player is somehow flipping from wallNormalPrevious to -wallNormalPrevious in a single frame.
    It also isn't compatible with input.pitch and input.yaw which are absolute and not differences.
    **So it might be easier to just answer this question.

    Hmm, what if...
    1. Take the player's current view (view in the last frame)
    2. Find the rotationAxis between the last frame's wallNormal and the current frame's wallNormal (rotationAxis = wallNormalCurrent x wallNormalPrevious)
    3. Find the rotationAngle between the last frame's wallNormal and the current frame's wallNormal (angle = arccos(wallNormalCurrent . wallNormalPrevious))
    4. Find the difference between the last frame's input.pitch and input.yaw and the current frame's input.pitch and input.yaw
    5. Rotate the player's current view around rotationAxis for rotationAngle
    6. Rotate the player's new view around wallNormal for the input.yaw difference
    7. Rotate the player's new view around the player's new x-axis for the input.pitch difference
    * 1, 2/3, and 4 (player's view, wallNormal, input.pitch, input.yaw; from the last frame) would have had to be previously stored.

    Another idea...
    - Two cases: wallnormal.y is positive or zero, wallnormal.y is negative.
    - Do the same code for each case, just flipped around, making sure the transition between the two cases is essentially continuous.
    - Case 1: Start on the ground facing in the z direction; take cross and dot products with [0,1,0]
    - Case 2: Start on the ground facing in the z direction (i.e. set rotation to 180 degrees around z-axis); take cross and dot products with [0,-1,0] * x- and y-axis negative.

    Another idea...
    Could look at the <a href="http://www.unknownworlds.com/ns2/forums/index.php?showtopic=115926&view=findpost&p=1897270" target="_blank">AvP code</a> again, maybe.
  • HarimauHarimau Join Date: 2007-12-24 Member: 63250Members
    edited February 2012
    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->//    local storeCoords = self:GetCoords() // last frame's player's view, used for z-axis compensation
        local coords = self:GetCoords() // last frame's player's view, used as a basis

        // if no previous value of wallNormalLast, wallNormalLast = self.wallWalkingNormalCurrent

        local rotationAxis = self.wallWalkingNormalCurrent:CrossProduct(wallNormalLast)
        if rotationAxis = Vector(0,0,0) // if flipping 180 degrees in one frame
            rotationAxis = coords.xAxis // just rotate around player's base x-axis, should be looked at
        end

        local dot1 = self.wallWalkingNormalCurrent:DotProduct(wallNormalLast)
        local rotationAngle = math.acos(dot1)

        coords = coords*coords.GetRotation(rotationAxis,rotationAngle)

        local yawDiff = input.yaw - yawLast // if no previous value of yawLast, yawLast = 0
        local pitchDiff = input.pitch - pitchLast // if no previous value of pitchLast, pitchLast = 0

        coords = coords*coords.GetRotation(self.wallWalkingNormalCurrent,yawDiff)*coords.GetRotation(coords.xAxis,pitchDiff)

    //    // retain z-axis
    //    if self:GetIsViewLocked() // made this up: true or false - depends on if the player has the "lock view" option on
    //        local compensationAxis = storeCoords.zAxis:CrossProduct(coords.zAxis)
    //        if compensationAxis = Vector(0,0,0) // if flipping 180 degrees in one frame
    //            compensationAxis = storeCoords.xAxis // just rotate around player's current x-axis, should be looked at
    //        end
    //
    //        local dot2 = storeCoords.zAxis:CrossProduct(coords.zAxis)
    //        local compensationAngle = math.acos(dot2)
    //
    //        coords = coords*coords.GetRotation(compensationAxis,compensationAngle)
    //    end

        angles:BuildFromCoords(coords)
        self:SetViewAngles(angles) // check that this function does what we want it to

        // store values for next frame:
        wallNormalLast = self.wallWalkingNormalCurrent
        yawLast = input.yaw
        pitchLast = input.pitch<!--c2--></div><!--ec2-->
    Realised there may be an issue with the z-axis compensation - it might reverse any input.yaw and input.pitch changes, so gonna have to rework that.
  • YuukiYuuki Join Date: 2010-11-20 Member: 75079Members
    <!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec-->Which video? Which code approach?<!--QuoteEnd--></div><!--QuoteEEnd-->

    I was referring to post 66 :

    <a href="http://www.unknownworlds.com/ns2/forums/index.php?showtopic=115601&view=findpost&p=1898239" target="_blank">http://www.unknownworlds.com/ns2/forums/in...t&p=1898239</a>

    One problem I often get with differential approach, when you got something like newCoord = f(oldCoords) get unstable (it's a kind of differential equation) and your view spin in a crazy fashion, it's a pain to debug. But it might be a way to unsure continuity yeah.
  • YuukiYuuki Join Date: 2010-11-20 Member: 75079Members
    edited February 2012
    Did the matlab implementation of your last ideas, and it seems to work well; the transitions are smooth and there is not problem (at least with the matlab implementation) when the wall normal is up or down, the axis take a bit weird orientations sometimes so I don't know how it will look in game.

    EDIT: Ups, there is plenty of problems when the normal is totally vertical.. I'm not sure how to deal with it.

    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->pitch =  pi;  %between 0 (up) and pi (down)
    yaw   =  1;   %any values

    wallNormal = zeros(1,3);

    wallNormal(1) =  sin(pitch) * cos(yaw);
    wallNormal(2) =  sin(pitch) * sin(yaw);
    wallNormal(3) =  cos(pitch);

    wallNormal =  wallNormal / norm(wallNormal);

    clf
    hold on
    plot3([0 wallNormal(1)],[0 wallNormal(2)],[0 wallNormal(3)])

    %plot absolute coordinate system
    plot3([0 1],[0 0],[0 0],'k')
    plot3([0 0],[0 1],[0 0],'k')
    plot3([0 0],[0 0],[0 1],'k')

    %build up rotated coordinate system
    x = [1 0 0]';
    y = [0 1 0]';
    z = [0 0 1]';

    p = cross(z,wallNormal);
    th = acos(dot(wallNormal,z));

    if( abs(dot(wallNormal,z)) < 0.999 )
        p = p ./ norm(p);
        
        R = vrrotvec2mat([p th]);
        x = R*x; y = R*y; z = R*z;

    else
        if dot(wallNormal,z) < 0  
            p = [0 0 1];

            R = vrrotvec2mat([p pi/2]);
            x = R*x; y = R*y; z = R*z;
            
            p = [1 0 0];

            R = vrrotvec2mat([p pi]);
            x = R*x; y = R*y; z = R*z;
        end
    end



    x=0.5*x; y=0.5*y; z=0.5*z;

    plot3([0 x(1)],[0 x(2)],[0 x(3)],'r','lineWidth',2); text(x(1),x(2),x(3),'x')
    plot3([0 y(1)],[0 y(2)],[0 y(3)],'g','lineWidth',2); text(y(1),y(2),y(3),'y')
    plot3([0 z(1)],[0 z(2)],[0 z(3)],'b','lineWidth',2); text(z(1),z(2),z(3),'z')

    xlabel('x')
    ylabel('y')
    zlabel('z')

    view(60,30)
    axis([-1 1 -1 1 -1 1]/1.5)
    axis vis3d<!--c2--></div><!--ec2-->
  • Fluid CoreFluid Core Join Date: 2007-12-26 Member: 63260Members, Reinforced - Shadow
    <!--quoteo(post=1898712:date=Feb 2 2012, 02:01 AM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Feb 2 2012, 02:01 AM) <a href="index.php?act=findpost&pid=1898712"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->The only time the system would break is if a player is somehow flipping from wallNormalPrevious to -wallNormalPrevious in a single frame.<!--QuoteEnd--></div><!--QuoteEEnd-->

    Isn't that what will happen if we stand on a flat ceiling and detach from it? I would think that your "wallwalkingnormal" go back to floor-mode when you are in the air.


    <!--quoteo(post=1898819:date=Feb 2 2012, 02:37 PM:name=Yuuki)--><div class='quotetop'>QUOTE (Yuuki @ Feb 2 2012, 02:37 PM) <a href="index.php?act=findpost&pid=1898819"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->EDIT: Ups, there is plenty of problems when the normal is totally vertical.. I'm not sure how to deal with it.<!--QuoteEnd--></div><!--QuoteEEnd-->

    What happens when the normal is totally vertical? I might have to crack up my MATLAB if it's hard to explain in words :)
  • YuukiYuuki Join Date: 2010-11-20 Member: 75079Members
    <!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec-->What happens when the normal is totally vertical? I might have to crack up my MATLAB if it's hard to explain in words :)<!--QuoteEnd--></div><!--QuoteEEnd-->

    Well the length of the cross product p becomes zero, so you don't have an axis to do the rotation anymore.

    p = cross(z,wallNormal);

    When the wall normal is up it's ok because doing nothing works. But when the wall normal is down you need to do something, and I don't really know what.

    Playing with the matlab code helps to understand. To see what happens you need to set pitch = pi, and yaw at some values (need to test several ones, positive negative) and then to make little perturbation of the pitch :

    pitch = pi + 0.1
    pitch = pi - 0.1

    and see if the axis transition smoothly, which is not the case for negative yaw, they flip around.
  • YuukiYuuki Join Date: 2010-11-20 Member: 75079Members
    Hum doesn't work. It keeps well your y axis (in blue) to the wall normal (cian) but messes with your yaw (second image) :

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

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

    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->    self:SetBaseViewAngles(Angles(0,0,0))
            
        local currentCoords = self:GetViewAngles():GetCoords()
        
        local angles = Angles(0,0,0)
        local absCoords = angles:GetCoords()
        
        absCoords.xAxis = Vector(1,0,0)
        absCoords.yAxis = Vector(0,1,0)
        absCoords.zAxis = Vector(0,0,1)
        
        angles = self:ConvertToViewAngles(input.pitch, input.yaw, 0)
        local defaultCoords = angles:GetCoords()
        
        local wallNormal = self.wallWalkingNormalCurrent
        

        //build up wall coordinate system
        local wallCoords = angles:GetCoords()
        wallCoords.xAxis = Vector(1,0,0)
        wallCoords.yAxis = Vector(0,1,0)
        wallCoords.zAxis = Vector(0,0,1)
        
        local p = absCoords.yAxis:CrossProduct(wallNormal)
        local th = math.acos(absCoords.yAxis:DotProduct(wallNormal))
        
        wallCoords = Coords.GetRotation(p, th)*wallCoords
        
        local tmp = CopyCoords(wallCoords)
        local rotCoords = CopyCoords(wallCoords)
        
        rotCoords = Coords.GetRotation(rotCoords.xAxis, input.pitch)*rotCoords
        rotCoords = Coords.GetRotation(tmp.yAxis, input.yaw)*rotCoords

        angles:BuildFromCoords(defaultCoords)
        self:SetViewAngles(angles)
        
        //Display the different coordinate system
        if math.random() < 0.5 then
            local startPoint = Vector(self:GetOrigin())
            local extents = self:GetExtents()
            local endPoint = Vector(0,0,0)
            
            //wall normal
            endPoint = startPoint + wallNormal    
            DebugLine(startPoint, endPoint, .2, 1, 0, 1, 1)
            
            
            //wallCoords
            startPoint = Vector(self:GetOrigin())
            startPoint = startPoint
            
            endPoint = startPoint + 2*wallCoords.zAxis        
            DebugLine(startPoint, endPoint, .1, 1, 0, 0, 1)
            endPoint = startPoint + 2*wallCoords.xAxis
            DebugLine(startPoint, endPoint, .1, 0, 1, 0, 1)
            endPoint = startPoint + 2*wallCoords.yAxis
            DebugLine(startPoint, endPoint, .1, 0, 0, 1, 1)

        end<!--c2--></div><!--ec2-->
  • HarimauHarimau Join Date: 2007-12-24 Member: 63250Members
    edited February 2012
    Which approach of mine did you just try? The update-state version (second-last approach), or the modify-state "differential" version (last approach)?
    Judging by the code you pasted, it seems like the former. The difference from my code that I can see is that you did the rotation of input.pitch and input.yaw after establishing the base angles, like we've been doing before.

    @Fluid_Core: In which case, I just added a condition whereby it rotates around the player's x-axis.

    <!--quoteo(post=1898857:date=Feb 2 2012, 11:56 PM:name=Yuuki)--><div class='quotetop'>QUOTE (Yuuki @ Feb 2 2012, 11:56 PM) <a href="index.php?act=findpost&pid=1898857"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->When the wall normal is up it's ok because doing nothing works. But when the wall normal is down you need to do something, and I don't really know what.<!--QuoteEnd--></div><!--QuoteEEnd-->
    What I have been doing is just arbitrarily specifying an axis of rotation.
    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->    if rotationAxis = Vector(0,0,0)
            rotationAxis = coords.xAxis // just rotate around player's base x-axis, should be looked at
        end<!--c2--></div><!--ec2-->
    The best axes to use would be the player's xAxis (so they flip), or zAxis (so they roll).

    With respect to the yaw problem (the x and z-axes), I think I know what that's being caused by.
    Imagine you were inside a large hemispherical bowl: at the bottom of the bowl, you're facing forward: you walk forward and at the front of the bowl, you're facing upward; you walk backward and at the back of the bowl, you're facing downward; you strafe left and at the left side of the bowl, you're facing forward; you strafe right and at the right side of the bowl, you're facing forward. Now, what about if you were between the front and left side of the bowl? Naturally it would be the average: you are facing between up and forward.

    The differential approach is looking more and more attractive, I think. The differential approach essentially resets the absolute frame of reference to your personal frame of reference with each new game frame (you start at the bottom of the bowl with each new frame).

    <!--quoteo(post=1898716:date=Feb 2 2012, 09:18 AM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Feb 2 2012, 09:18 AM) <a href="index.php?act=findpost&pid=1898716"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec--><!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->//    local storeCoords = self:GetViewAngles():GetCoords() // last frame's player's view, used for z-axis compensation
        local coords = self:GetViewAngles():GetCoords() // last frame's player's view, used as a basis

        // if no previous value of wallNormalLast, wallNormalLast = self.wallWalkingNormalCurrent

        local rotationAxis = self.wallWalkingNormalCurrent:CrossProduct(wallNormalLast)
        if rotationAxis = Vector(0,0,0) // if flipping 180 degrees in one frame
            rotationAxis = coords.xAxis // just rotate around player's base x-axis, should be looked at
        end

        local dot1 = self.wallWalkingNormalCurrent:DotProduct(wallNormalLast)
        local rotationAngle = math.acos(dot1)

        coords = coords*coords.GetRotation(rotationAxis,rotationAngle)

        local yawDiff = input.yaw - yawLast // if no previous value of yawLast, yawLast = input.yaw (yawDiff = 0)
        local pitchDiff = input.pitch - pitchLast // if no previous value of pitchLast, pitchLast = input.pitch (pitchDiff = 0)

        coords = coords*coords.GetRotation(self.wallWalkingNormalCurrent,yawDiff)*coords.GetRotation(coords.xAxis,pitchDiff)

    //    // retain z-axis
    //    if self:GetIsViewLocked() // made this up: true or false - depends on if the player has the "lock view" option on
    //        local compensationAxis = storeCoords.zAxis:CrossProduct(coords.zAxis)
    //        if compensationAxis = Vector(0,0,0) // if flipping 180 degrees in one frame
    //            compensationAxis = storeCoords.xAxis // just rotate around player's current x-axis, should be looked at
    //        end
    //
    //        local dot2 = storeCoords.zAxis:CrossProduct(coords.zAxis)
    //        local compensationAngle = math.acos(dot2)
    //
    //        coords = coords*coords.GetRotation(compensationAxis,compensationAngle)
    //    end

        angles:BuildFromCoords(coords)
        self:SetViewAngles(angles) // check that this function does what we want it to

        // store values for next frame:
        wallNormalLast = self.wallWalkingNormalCurrent
        yawLast = input.yaw
        pitchLast = input.pitch<!--c2--></div><!--ec2-->
    EDIT:(made a couple of corrections)
    Realised there may be an issue with the z-axis compensation - it might reverse any input.yaw and input.pitch changes, so gonna have to rework that.<!--QuoteEnd--></div><!--QuoteEEnd-->
    May need some debugging, like making sure that wallNormalLast, yawLast and pitchLast have a first value (for the very first frame).
    Something like
    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// put this at the start of the code
    if variable is nil // wallnormalLast, yawLast, pitchLast
    variable = default value // self.wallWalkingNormalCurrent, input.pitch, input.yaw
    end<!--c2--></div><!--ec2-->
    Also need to ensure I've got the vectors in the right order for the cross product.



    You know, I'm quite curious about this, but doesn't the model's angles already correspond to what we want the camera's view angles to be? Can't we somehow just set the camera's view angles to be the model's angles?
  • NurEinMenschNurEinMensch Join Date: 2003-02-26 Member: 14056Members, Constellation
    Just wanted to let you guys know that I love reading this applied math stuff. =)
  • YuukiYuuki Join Date: 2010-11-20 Member: 75079Members
    edited February 2012
    Good, at least all of this is not completely useless then :)

    Tremulous has also some wall walking stuff, the code is "relatively clear", and they seem to had
    also some problems to do it :

    <!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec-->//behold the evil mind###### from hell
    //it has ###### mind like nothing has ###### mind before<!--QuoteEnd--></div><!--QuoteEEnd-->

    :D

    <a href="http://tremulous.cvs.sourceforge.net/viewvc/tremulous/wallwalk/src/game/bg_pmove.c" target="_blank">http://tremulous.cvs.sourceforge.net/viewv...game/bg_pmove.c</a>

    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->      //if the trace result and old surface normal are different then we must have transided to a new
          //surface... do some stuff...
          if( !VectorCompare( trace.plane.normal, surfNormal ) )
          {
            //if the trace result or the old vector is not the floor or ceiling correct the YAW angle
            if( !VectorCompare( trace.plane.normal, refNormal ) && !VectorCompare( surfNormal, refNormal ) &&
                !VectorCompare( trace.plane.normal, ceilingNormal ) && !VectorCompare( surfNormal, ceilingNormal ) )
            {
              //behold the evil mind###### from hell
              //it has ###### mind like nothing has ###### mind before
                
              //calculate reference rotated through to trace plane
              RotatePointAroundVector( refTOtrace, traceCROSSref, horizontal, -traceANGref );
              
              //calculate reference rotated through to surf plane then to trace plane
              RotatePointAroundVector( tempVec, surfCROSSref, horizontal, -surfANGref );
              RotatePointAroundVector( refTOsurfTOtrace, traceCROSSsurf, tempVec, -traceANGsurf );

              //calculate angle between refTOtrace and refTOsurfTOtrace
              rTtDOTrTsTt = DotProduct( refTOtrace, refTOsurfTOtrace );
              rTtANGrTsTt = ANGLE2SHORT( RAD2DEG( acos( rTtDOTrTsTt ) ) );

              if( rTtANGrTsTt > 32768 )
                rTtANGrTsTt -= 32768;

              //set the correction angle
              if( traceCROSSsurf[ 2 ] < 0 )
                rTtANGrTsTt = -rTtANGrTsTt;

              //phew! - correct the angle
              pm->ps->delta_angles[ YAW ] -= rTtANGrTsTt;
            }
            
            //construct a plane dividing the surf and trace normals
            CrossProduct( traceCROSSsurf, surfNormal, abc );
            VectorNormalize( abc );
            d = DotProduct( abc, pm->ps->origin );

            //construct a point representing where the player is looking
            VectorAdd( pm->ps->origin, lookdir, point );
            
            //check whether point is on one side of the plane, if so invert the correction angle
            if( ( abc[ 0 ] * point[ 0 ] + abc[ 1 ] * point[ 1 ] + abc[ 2 ] * point[ 2 ] - d ) > 0 )
              traceANGsurf = -traceANGsurf;
            
            //find the . product of the lookdir and traceCROSSsurf
            if( ( ldDOTtCs = DotProduct( lookdir, traceCROSSsurf ) ) < 0.0f )
            {
              VectorInverse( traceCROSSsurf );
              ldDOTtCs = DotProduct( lookdir, traceCROSSsurf );
            }
            
            //set the correction angle
            traceANGsurf *= 1.0f - ldDOTtCs;

            //correct the angle
            pm->ps->delta_angles[ PITCH ] -= ANGLE2SHORT( traceANGsurf );

            //transition from wall to ceiling
            //normal for subsequent viewangle rotations
            if( VectorCompare( trace.plane.normal, ceilingNormal ) )
            {
              CrossProduct( surfNormal, trace.plane.normal, pm->ps->grapplePoint );
              VectorNormalize( pm->ps->grapplePoint );
              pm->ps->pm_flags |= PMF_WALLWALKINGCEILING;
            }

            //transition from ceiling to wall
            //we need to do some different angle correction here cos GPISROTVEC
            if( VectorCompare( surfNormal, ceilingNormal ) )
            {
              vectoangles( trace.plane.normal, toAngles );
              vectoangles( pm->ps->grapplePoint, surfAngles );

              pm->ps->delta_angles[1] -= ANGLE2SHORT( ( ( surfAngles[1] - toAngles[1] ) * 2 ) - 180 );
            }
          }<!--c2--></div><!--ec2-->
  • HarimauHarimau Join Date: 2007-12-24 Member: 63250Members
    edited February 2012
    I didn't find that very clear*, so I just skimmed it...
    *<!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->so much alternating of big and small letters, it's like the year 2000 all over again...<!--sizec--></span><!--/sizec-->

    But as far as I can tell, they're using a <a href="http://www.unknownworlds.com/ns2/forums/index.php?showtopic=115601&view=findpost&p=1899088" target="_blank">differential approach</a>.

    Before attempting that though, we should first consider this:
    <!--quoteo--><div class='quotetop'>QUOTE </div><div class='quotemain'><!--quotec-->You know, I'm quite curious about this, but doesn't the model's angles already correspond to what we want the camera's view angles to be? Can't we somehow just set the camera's view angles to be the model's angles?<!--QuoteEnd--></div><!--QuoteEEnd-->
  • EstevooEstevoo Join Date: 2011-12-25 Member: 138993Members
    Do you have something like that for NS1???
  • HarimauHarimau Join Date: 2007-12-24 Member: 63250Members
    What do you mean? NS1 didn't have view rotation.
  • Soul_RiderSoul_Rider Mod Bean Join Date: 2004-06-19 Member: 29388Members, Constellation, Squad Five Blue
    edited August 2012
    OK, so with Yuuki bringing this up to me the other day, and Zaggy creating a new thread to discuss it, maybe it's time we had a crack at solving the view rotation? I can run it in proving grounds to test playability of it, I just think this could be the icing on the NS2 v1.0 cake if we could get it working :)

    ------------------------------------------------------------

    Edit:

    Just opened up skulk_client.lua, looked at the code and nearly had a heart attack, oh the fear!!!!

    I have uploaded the ns2_skulktest map I made for testing view rotations previously to Duplex. If you attempt to try any codework on this project, I strongly advise to use this quick loading, wall walking designed map :)

    <a href="http://www.duplexgaming.co.uk/downloads/maps/113/ns2_skulktest/" target="_blank">Download Here</a>
Sign In or Register to comment.