[RFC 8/11] Linux FF: Linux effect direction

Elias Vanderstuyft elias.vds at gmail.com
Tue Mar 4 13:20:22 CST 2014


a)  Some common mistakes about linux directions:
    - using 0x7FFF as ~359 degrees for linux direction, instead of
0xFFFF (look at /include/uapi/linux/input.h:1117)
    - the everlasting confusion about how to map dinput to linux directions,
        this proves to be a difficult topic, only by finding out that
the SDL2 haptic implementation does a different conversion than Wine
does,
        and since only one possibility is the right one, at least one
of the implementations is wrong.
        It turns out they're both wrong.
        With this explanation, I try to finally come up with the right
implementation, by deriving it in a systematical way.
        This explanation has been verified by the linux input mailing
lists: http://www.mail-archive.com/linux-input@vger.kernel.org/msg08459.html

        Directions
        ==========

        For conversions between different APIs, the physical behaviour
should be the same.

        DInput
        ______

        Extracted from
http://msdn.microsoft.com/en-us/library/windows/desktop/ee417536%28v=vs.85%29.aspx
:

        Physical situation:


                 North (polar 0, spher[0] 270)
                    (0, -1)
                    -y Far

          West              East (polar 90, spher[0] 0)
        (-1, 0)        O       (+1, 0)
        -x Left                +x Right

                     South
                    (0, +1)
                    +y Near


                    .------.
                    | User |
                    '------'

        Remember these are the 'directions':
            as defined as the direction the user should exert force in
to counteract the force.
            So the direction in which the force is applied by the
joystick, is opposite.

        If we're interested in the direction in which the force is
applied by the joystick,
        the physical situation becomes point-flipped:


                          (polar 0, spher[0] 270)
                                  (0, +1)
                                    +y

        (polar 270, spher[0] 180)           (polar 90, spher[0] 0)
                (+1, 0)              O             (-1, 0)
                   +x                                -x

                          (polar 180, spher[0] 90)
                                  (0, -1)
                                    -y

        Linux
        _____

        Extracted from Linux /include/uapi/linux/input.h:1113, and
from the discussion with Anssi Hannula in the mailing lists :

        Physical situation:


                    Up (dir 180)
                      (0, -1)
                        -y

        Left (dir 90)           Right (dir 270)
          (-1, 0)        O          (+1, 0)
            -x                        +x

                  Down (dir 0)
                      (0, +1)
                        +y


                     .------.
                     | User |
                     '------'

        It is unclear what is meant by 'directions' in this case:
            Possibility #1: counteraction direction of user
            Possibility #2: direction of force applied by joystick

        Anssi Hannula confirmed that Possibility #2 is the right one.

        Mapping
        _______

        In the "Carts" column, you can find the Cartesian coordinates
which indicate the counteraction direction of user.
        (This choice is arbitrary ('direction of force applied by
joystick' could also have been chosen), just to have a physical
reference.)
        These "Carts" tuples should be equal across APIs to ensure
equal behavior.

        #Assume Possibility #1:
        #
        #          DInput         <->     Linux
        #    _________________     |   ___________
        #    Carts|Polar|Spher     |   Carts|Direc
        #    -----+-----+-----     |   -----+-----
        #    0, -1|   0 | 270      |   0, -1| 180
        #    +1, 0|  90 |   0      |   +1, 0| 270
        #    0, +1| 180 |  90      |   0, +1|   0
        #    -1, 0| 270 | 180      |   -1, 0|  90

        Assume Possibility #2:
            => Linux Cartesian coordinates are opposite in respect to
the "Carts" reference.

                  DInput         <->     Linux
            _________________     |   ___________
            Carts|Polar|Spher     |   Carts|Direc
            -----+-----+-----     |   -----+-----
            0, -1|   0 | 270      |   0, -1|   0
            +1, 0|  90 |   0      |   +1, 0|  90
            0, +1| 180 |  90      |   0, +1| 180
            -1, 0| 270 | 180      |   -1, 0| 270

        This conversion table will be used in this document.

    On effect_linuxinput.c:385, there is written:
        /* some of this may look funky, but it's 'cause the linux
driver and directx have
         * different opinions about which way direction "0" is.
directx has 0 along the x
         * axis (left), linux has it along the y axis (down). */
    The part "directx has 0 along the x axis (left)" contradicts with
"angle bases: 0 -> -y (down) (linux) -> 0 -> +x (right) (windows)" on
line 148,
    because 'left' != 'right'.
    Also, this comment seems to be about the dinput spherical
coordinates, instead of polar.
    Applying the conversion table on dinput/effect_linuxinput.c:385, change :
        /* some of this may look funky, but it's 'cause the linux
driver and directx have
         * different opinions about which way direction "0" is.
directx has 0 along the x
         * axis (left), linux has it along the y axis (down). */
        if (dwFlags & DIEP_DIRECTION) {
            if (peff->cAxes == 1) {
                if (peff->dwFlags & DIEFF_CARTESIAN) {
                    if (dwFlags & DIEP_AXES) {
                        if (peff->rgdwAxes[0] == DIJOFS_X &&
peff->rglDirection[0] >= 0)
                            This->effect.direction = 0x4000;
                        else if (peff->rgdwAxes[0] == DIJOFS_X &&
peff->rglDirection[0] < 0)
                            This->effect.direction = 0xC000;
                        else if (peff->rgdwAxes[0] == DIJOFS_Y &&
peff->rglDirection[0] >= 0)
                            This->effect.direction = 0;
                        else if (peff->rgdwAxes[0] == DIJOFS_Y &&
peff->rglDirection[0] < 0)
                            This->effect.direction = 0x8000;
    to :
        /* some of this may look funky, but it's 'cause the linux
driver and directx have
         * different opinions about which way direction "0" is.
directx has 0 along the y
         * axis (north/far, counteract), linux has it along the y axis
(down). */
        if (dwFlags & DIEP_DIRECTION) {
            if (peff->cAxes == 1) {
                if (peff->dwFlags & DIEFF_CARTESIAN) {
                    if (peff->rgdwAxes[0] == DIJOFS_X &&
peff->rglDirection[0] >= 0)
                        This->effect.direction = 0x4000;
                    else if (peff->rgdwAxes[0] == DIJOFS_X &&
peff->rglDirection[0] < 0)
                        This->effect.direction = 0xC000;
                    else if (peff->rgdwAxes[0] == DIJOFS_Y &&
peff->rglDirection[0] >= 0)
                        This->effect.direction = 0x8000;
                    else if (peff->rgdwAxes[0] == DIJOFS_Y &&
peff->rglDirection[0] < 0)
                        This->effect.direction = 0;

    Change dinput/effect_linuxinput.c:148 :
        /* Major conversion factors are:
         * times: millisecond (linux) -> microsecond (windows) (x * 1000)
         * forces: scale 0x7FFF (linux) -> scale 10000 (windows)
approx ((x / 33) * 10)
         * angles: scale 0x7FFF (linux) -> scale 35999 (windows)
approx ((x / 33) * 36)
         * angle bases: 0 -> -y (down) (linux) -> 0 -> +x (right) (windows)
         */
    to :
        /* Major conversion factors are:
         * times: millisecond (linux) -> microsecond (windows) (x * 1000)
         * forces: scale 0x7FFF (linux) -> scale 10000 (windows)
approx ((x / 33) * 10)
         * angles: scale 0xFFFF (linux) -> scale 35999 (windows)
approx ((x / 66) * 36)
         * angle bases: 0 -> +y (down) (linux) -> 0 -> -y (far:
counteract direction) (windows polar)
         */

    and change dinput/effect_linuxinput.c:184 :
        peff->rglDirection[0] = (This->effect.direction / 33) * 36 + 9000;
        if (peff->rglDirection[0] > 35999)
            peff->rglDirection[0] -= 35999;
    to :
        peff->rglDirection[0] = (This->effect.direction / 66) * 36;

    and change dinput/effect_linuxinput.c:415 :
        This->effect.direction = (int)((3 * M_PI / 2 - atan2(y, x)) *
-0x7FFF / M_PI);
    to :
        This->effect.direction = (unsigned int)((M_PI / 2 + atan2(y,
x)) * 0x8000 / M_PI);

    and change dinput/effect_linuxinput.c:505 :
        /* One condition block.  This needs to be rotated to direction,
         * and expanded to separate x and y conditions. */
        int i;
        double factor[2];
        factor[0] = asin((This->effect.direction * 3.0 * M_PI) / 0x7FFF);
        factor[1] = acos((This->effect.direction * 3.0 * M_PI) / 0x7FFF);
    to :
        /* One condition block.  This needs to be rotated to direction,
         * using dinput Cartesian coordinates, and expanded to
separate x and y conditions. */
        int i;
        double factor[2];
        factor[0] = +sin((This->effect.direction * M_PI) / 0x8000);
        factor[1] = -cos((This->effect.direction * M_PI) / 0x8000);


b)  Applying direction seems to have no influence on X and Y force.
(It's always polar 0 degrees) Tested in FEdit
    => Multiple things in the implementation are wrong:
        - typo in the bracket placement for the double-to-int conversion
        - forgetting (typo) that dinput works with 9000 instead of 90
(inconsistent with linux->dinput conversion of GetParameters)
        - common mistakes about linux directions, see above
        - it is more readable and accurate to use "* (0x8000 / 18000)"
than "* (0xFFFF / 35999)",
        and wrapping of this (effect.direction) u16 is intentional.
    Solution: Change dinput/effect_linuxinput.c:419 :
        This->effect.direction = (int)(((double)peff->rglDirection[0]
- 90) / 35999) * 0x7FFF;
    to :
        This->effect.direction = (unsigned
int)(((double)peff->rglDirection[0] / 18000) * 0x8000);


c)  Multiple things are weird/wrong here:
    - common mistakes about linux directions, see above
    - "M_PI * 3", why '3' ???
    - Why "1000" as max magnitude of direction? type(rglDirection[i])
== 'LONG' => You can pick at least 10000 (DI_FFNOMINALMAX).
    So, change dinput/effect_linuxinput.c:174 :
        if (peff->dwFlags & DIEFF_CARTESIAN) {
            peff->rglDirection[0] = sin(M_PI * 3 *
This->effect.direction / 0x7FFF) * 1000;
            peff->rglDirection[1] = cos(M_PI * 3 *
This->effect.direction / 0x7FFF) * 1000;
    to :
        if (peff->dwFlags & DIEFF_CARTESIAN) {
            peff->rglDirection[0] = +sin(This->effect.direction * M_PI
/ 0x8000) * DI_FFNOMINALMAX;
            peff->rglDirection[1] = -cos(This->effect.direction * M_PI
/ 0x8000) * DI_FFNOMINALMAX;


d)  The statement "Polar and spherical are the same for 2 axes" is wrong:
        http://msdn.microsoft.com/en-us/library/windows/desktop/ee417536%28v=vs.85%29.aspx
:
            "Polar coordinates are expressed as a single angle, in
hundredths of a degree clockwise from whatever zero-point, or true
north, has been established for the effect.
                Normally this is the negative y-axis; that is, away
from the user."
            => (0, -1) direction is Polar starting direction (angle = 0)
        http://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.reference.dieffect%28v=vs.85%29.aspx
:
            "If spherical, the first angle is measured in hundredths
of a degree from the (1, 0) direction, rotated in the direction of (0,
1)."
            => (1, 0) direction is Spherical starting direction (angle = 0)
        (also take a look at the conversion table)

    So, change dinput/effect_linuxinput.c:177 :
        } else {
            /* Polar and spherical coordinates are the same for two or less
             * axes.
             * Note that we also use this case if NO flags are marked.
             * According to MSDN, we should return the direction in the
             * format that it was specified in, if no flags are marked.
             */
            peff->rglDirection[0] = (This->effect.direction / 66) * 36;
        }
    to :
        } else if (peff->dwFlags & DIEFF_SPHERICAL) {
            peff->rglDirection[0] = 27000 + (This->effect.direction / 66) * 36;
            if (peff->rglDirection[0] >= 36000)
                peff->rglDirection[0] -= 36000;
        } else /* if (peff->dwFlags & DIEFF_POLAR) */ {
            /* Note that we return polar coordinates if NO flags are marked.
             * According to MSDN, we should return the direction in the
             * format that it was specified in, if no flags are marked.
             * TODO: Return the direction in the format that it was
specified in,
             *       if no flags are marked.
             */
            peff->rglDirection[0] = (This->effect.direction / 66) * 36;
        }

    And change dinput/effect_linuxinput.c:416 :
        } else {
            /* Polar and spherical are the same for 2 axes */
            /* Precision is important here, so we do double math with
exact constants */
            This->effect.direction = (unsigned
int)(((double)peff->rglDirection[0] / 18000) * 0x8000);
        }
    to :
        } else if (peff->dwFlags & DIEFF_SPHERICAL) {
            This->effect.direction = (unsigned int)(((9000 +
(double)peff->rglDirection[0]) / 18000) * 0x8000);
        } else /* if (peff->dwFlags & DIEFF_POLAR) */ {
            This->effect.direction = (unsigned
int)(((double)peff->rglDirection[0] / 18000) * 0x8000);
        }


e)  If "first_axis_is_x" in effect_linuxinput.c happens to be "false"
and "cAxes == 2",
    doesn't the assignment of "effect.direction" in SetParameters(...)
need additional transformations if "(peff->dwFlags & DIEFF_POLAR) !=
0"?
    Because then x and y axis are swapped.
    With swapped x and y axes, then the following conversions hold:

        #Assume Possibility #1:
        #
        #          DInput         <->     Linux
        #    _________________     |   ___________
        #    Carts|Polar|Spher     |   Carts|Direc
        #    -----+-----+-----     |   -----+-----
        #    -1, 0|   0 | 270      |   -1, 0|  90
        #    0, +1|  90 |   0      |   0, +1|   0
        #    +1, 0| 180 |  90      |   +1, 0| 270
        #    0, -1| 270 | 180      |   0, -1| 180

        Assume Possibility #2:
            => Linux Cartesian coordinates are opposite in respect to
the "Carts" reference.

                  DInput         <->     Linux
            _________________     |   ___________
            Carts|Polar|Spher     |   Carts|Direc
            -----+-----+-----     |   -----+-----
            -1, 0|   0 | 270      |   -1, 0| 270
            0, +1|  90 |   0      |   0, +1| 180
            +1, 0| 180 |  90      |   +1, 0|  90
            0, -1| 270 | 180      |   0, -1|   0

        (Possibility #2 is the correct one)

    So change dinput/effect_linuxinput.c:416 :
        } else if (peff->dwFlags & DIEFF_SPHERICAL) {
            This->effect.direction = (unsigned int)(((9000 +
(double)peff->rglDirection[0]) / 18000) * 0x8000);
        } else /* if (peff->dwFlags & DIEFF_POLAR) */ {
            This->effect.direction = (unsigned
int)(((double)peff->rglDirection[0] / 18000) * 0x8000);
        }
    to :
        } else if (peff->dwFlags & DIEFF_SPHERICAL) {
            This->effect.direction = (unsigned int)(((9000 +
(double)peff->rglDirection[0]) / 18000) * 0x8000);
            if (!This->first_axis_is_x)
                This->effect.direction = 0xC000 - This->effect.direction;
        } else /* if (peff->dwFlags & DIEFF_POLAR) */ {
            This->effect.direction = (unsigned
int)(((double)peff->rglDirection[0] / 18000) * 0x8000);
            if (!This->first_axis_is_x)
                This->effect.direction = 0xC000 - This->effect.direction;
        }

    Then we can simplify the following code of dinput/effect_linuxinput.c:405 :
        } else { /* two axes */
            if (peff->dwFlags & DIEFF_CARTESIAN) {
                LONG x, y;
                if (This->first_axis_is_x) {
                    x = peff->rglDirection[0];
                    y = peff->rglDirection[1];
                } else {
                    x = peff->rglDirection[1];
                    y = peff->rglDirection[0];
                }
                This->effect.direction = (unsigned int)((M_PI / 2 +
atan2(y, x)) * 0x8000 / M_PI);
            } else if (peff->dwFlags & DIEFF_SPHERICAL) {
                This->effect.direction = (unsigned int)(((9000 +
(double)peff->rglDirection[0]) / 18000) * 0x8000);
                if (!This->first_axis_is_x)
                    This->effect.direction = 0xC000 - This->effect.direction;
            } else /* if (peff->dwFlags & DIEFF_POLAR) */ {
                This->effect.direction = (unsigned
int)(((double)peff->rglDirection[0] / 18000) * 0x8000);
                if (!This->first_axis_is_x)
                    This->effect.direction = 0xC000 - This->effect.direction;
            }
        }
    to :
        } else { /* two axes */
            if (peff->dwFlags & DIEFF_CARTESIAN) {
                This->effect.direction = (unsigned int)((M_PI / 2 +
atan2(peff->rglDirection[1], peff->rglDirection[0])) * 0x8000 / M_PI);
            } else if (peff->dwFlags & DIEFF_SPHERICAL) {
                This->effect.direction = (unsigned int)(((9000 +
(double)peff->rglDirection[0]) / 18000) * 0x8000);
            } else /* if (peff->dwFlags & DIEFF_POLAR) */ {
                This->effect.direction = (unsigned
int)(((double)peff->rglDirection[0] / 18000) * 0x8000);
            }
            if (!This->first_axis_is_x)
                This->effect.direction = 0xC000 - This->effect.direction;
        }


f)  Condition effects (with struct-size of 2*sizeof(DICONDITION))
should keep "first_axis_is_x" into account:
    Change dinput/effect_linuxinput.c:520 :
        /* Two condition blocks.  Direct parameter copy. */
        int i;
        for (i = 0; i < 2; ++i) {
            This->effect.u.condition[i].center = (tsp[i].lOffset / 10) * 32;
            This->effect.u.condition[i].right_coeff =
(tsp[i].lPositiveCoefficient / 10) * 32;
            This->effect.u.condition[i].left_coeff =
(tsp[i].lNegativeCoefficient / 10) * 32;
            This->effect.u.condition[i].right_saturation =
(tsp[i].dwPositiveSaturation / 10) * 65;
            This->effect.u.condition[i].left_saturation =
(tsp[i].dwNegativeSaturation / 10) * 65;
            This->effect.u.condition[i].deadband = (tsp[i].lDeadBand / 10) * 65;
        }
    to :
        /* Two condition blocks.  Direct parameter copy, after a small
change of axes if needed. */
        int i, j;
        for (i = 0; i < 2; ++i) {
            j = (first_axis_is_x ? i : 1-i);
            This->effect.u.condition[j].center = (tsp[i].lOffset / 10) * 32;
            This->effect.u.condition[j].right_coeff =
(tsp[i].lPositiveCoefficient / 10) * 32;
            This->effect.u.condition[j].left_coeff =
(tsp[i].lNegativeCoefficient / 10) * 32;
            This->effect.u.condition[j].right_saturation =
(tsp[i].dwPositiveSaturation / 10) * 65;
            This->effect.u.condition[j].left_saturation =
(tsp[i].dwNegativeSaturation / 10) * 65;
            This->effect.u.condition[j].deadband = (tsp[i].lDeadBand / 10) * 65;
        }

    The condition effects with struct-size of 1*sizeof(DICONDITION)
don't need changes regarding "first_axis_is_x",
    because "factor[i]" depends on "effect.direction", which already
incorporates "first_axis_is_x".


g)  Condition effects (with struct-size of 1*sizeof(DICONDITION)) are
not transformed exactly right,
    look at "interactive.fig" for more understanding of the suggested code:
        - "deadband" can't be negative
        - for "left/right_coeff" and "left/right_saturation",
            if a direction along an axis is negative, "left" and
"right" should be swapped

    Change dinput/effect_linuxinput.c:511 :
        for (i = 0; i < 2; ++i) {
            This->effect.u.condition[i].center = (int)(factor[i] *
(tsp->lOffset / 10) * 32);
            This->effect.u.condition[i].right_coeff = (int)(factor[i]
* (tsp->lPositiveCoefficient / 10) * 32);
            This->effect.u.condition[i].left_coeff = (int)(factor[i] *
(tsp->lNegativeCoefficient / 10) * 32);
            This->effect.u.condition[i].right_saturation =
(int)(factor[i] * (tsp->dwPositiveSaturation / 10) * 65);
            This->effect.u.condition[i].left_saturation =
(int)(factor[i] * (tsp->dwNegativeSaturation / 10) * 65);
            This->effect.u.condition[i].deadband = (int)(factor[i] *
(tsp->lDeadBand / 10) * 65);
        }
    to :
        for (i = 0; i < 2; ++i) {
            This->effect.u.condition[i].center = (int)(factor[i] *
(tsp->lOffset / 10) * 32);
            This->effect.u.condition[i].right_coeff =
(int)(abs(factor[i]) * ((factor[i] >= 0 ? tsp->lPositiveCoefficient :
tsp->lNegativeCoefficient) / 10) * 32);
            This->effect.u.condition[i].left_coeff =
(int)(abs(factor[i]) * ((factor[i] >= 0 ? tsp->lNegativeCoefficient :
tsp->lPositiveCoefficient) / 10) * 32);
            This->effect.u.condition[i].right_saturation =
(int)(abs(factor[i]) * ((factor[i] >= 0 ? tsp->dwPositiveSaturation :
tsp->dwNegativeSaturation) / 10) * 65);
            This->effect.u.condition[i].left_saturation =
(int)(abs(factor[i]) * ((factor[i] >= 0 ? tsp->dwNegativeSaturation :
tsp->dwPositiveSaturation) / 10) * 65);
            This->effect.u.condition[i].deadband =
(int)(abs(factor[i]) * (tsp->lDeadBand / 10) * 65);
        }


Elias
-------------- next part --------------
/////////////////////////////    Linux effect direction    /////////////////////////////

a)  Some common mistakes about linux directions:
    - using 0x7FFF as ~359 degrees for linux direction, instead of 0xFFFF (look at /include/uapi/linux/input.h:1117)
    - the everlasting confusion about how to map dinput to linux directions,
        this proves to be a difficult topic, only by finding out that the SDL2 haptic implementation does a different conversion than Wine does,
        and since only one possibility is the right one, at least one of the implementations is wrong.
        It turns out they're both wrong.
        With this explanation, I try to finally come up with the right implementation, by deriving it in a systematical way.
        This explanation has been verified by the linux input mailing lists: http://www.mail-archive.com/linux-input@vger.kernel.org/msg08459.html
    
        Directions
        ==========

        For conversions between different APIs, the physical behaviour should be the same.

        DInput
        ______

        Extracted from http://msdn.microsoft.com/en-us/library/windows/desktop/ee417536%28v=vs.85%29.aspx :

        Physical situation:


                 North (polar 0, spher[0] 270)
                    (0, -1)
                    -y Far

          West              East (polar 90, spher[0] 0)
        (-1, 0)        O       (+1, 0)
        -x Left                +x Right

                     South
                    (0, +1)
                    +y Near


                    .------.
                    | User |
                    '------'

        Remember these are the 'directions':
            as defined as the direction the user should exert force in to counteract the force.
            So the direction in which the force is applied by the joystick, is opposite.

        If we're interested in the direction in which the force is applied by the joystick,
        the physical situation becomes point-flipped:


                          (polar 0, spher[0] 270)
                                  (0, +1)
                                    +y

        (polar 270, spher[0] 180)           (polar 90, spher[0] 0)
                (+1, 0)              O             (-1, 0)
                   +x                                -x

                          (polar 180, spher[0] 90)
                                  (0, -1)
                                    -y

        Linux
        _____

        Extracted from Linux /include/uapi/linux/input.h:1113, and from the discussion with Anssi Hannula in the mailing lists :

        Physical situation:


                    Up (dir 180)
                      (0, -1)
                        -y
                      
        Left (dir 90)           Right (dir 270)
          (-1, 0)        O          (+1, 0)
            -x                        +x 
                  
                  Down (dir 0)
                      (0, +1)
                        +y


                     .------.
                     | User |
                     '------'

        It is unclear what is meant by 'directions' in this case:
            Possibility #1: counteraction direction of user
            Possibility #2: direction of force applied by joystick

        Anssi Hannula confirmed that Possibility #2 is the right one.

        Mapping
        _______

        In the "Carts" column, you can find the Cartesian coordinates which indicate the counteraction direction of user.
        (This choice is arbitrary ('direction of force applied by joystick' could also have been chosen), just to have a physical reference.)
        These "Carts" tuples should be equal across APIs to ensure equal behavior.

        #Assume Possibility #1:
        #
        #          DInput         <->     Linux
        #    _________________     |   ___________
        #    Carts|Polar|Spher     |   Carts|Direc
        #    -----+-----+-----     |   -----+-----
        #    0, -1|   0 | 270      |   0, -1| 180 
        #    +1, 0|  90 |   0      |   +1, 0| 270 
        #    0, +1| 180 |  90      |   0, +1|   0 
        #    -1, 0| 270 | 180      |   -1, 0|  90 

        Assume Possibility #2:
            => Linux Cartesian coordinates are opposite in respect to the "Carts" reference.
            
                  DInput         <->     Linux
            _________________     |   ___________
            Carts|Polar|Spher     |   Carts|Direc
            -----+-----+-----     |   -----+-----
            0, -1|   0 | 270      |   0, -1|   0 
            +1, 0|  90 |   0      |   +1, 0|  90 
            0, +1| 180 |  90      |   0, +1| 180 
            -1, 0| 270 | 180      |   -1, 0| 270 

        This conversion table will be used in this document.
    
    On effect_linuxinput.c:385, there is written:
        /* some of this may look funky, but it's 'cause the linux driver and directx have
         * different opinions about which way direction "0" is.  directx has 0 along the x
         * axis (left), linux has it along the y axis (down). */
    The part "directx has 0 along the x axis (left)" contradicts with "angle bases: 0 -> -y (down) (linux) -> 0 -> +x (right) (windows)" on line 148,
    because 'left' != 'right'.
    Also, this comment seems to be about the dinput spherical coordinates, instead of polar.
    Applying the conversion table on dinput/effect_linuxinput.c:385, change :
        /* some of this may look funky, but it's 'cause the linux driver and directx have
         * different opinions about which way direction "0" is.  directx has 0 along the x
         * axis (left), linux has it along the y axis (down). */
        if (dwFlags & DIEP_DIRECTION) {
            if (peff->cAxes == 1) {
                if (peff->dwFlags & DIEFF_CARTESIAN) {
                    if (dwFlags & DIEP_AXES) {
                        if (peff->rgdwAxes[0] == DIJOFS_X && peff->rglDirection[0] >= 0)
                            This->effect.direction = 0x4000;
                        else if (peff->rgdwAxes[0] == DIJOFS_X && peff->rglDirection[0] < 0)
                            This->effect.direction = 0xC000;
                        else if (peff->rgdwAxes[0] == DIJOFS_Y && peff->rglDirection[0] >= 0)
                            This->effect.direction = 0;
                        else if (peff->rgdwAxes[0] == DIJOFS_Y && peff->rglDirection[0] < 0)
                            This->effect.direction = 0x8000;
    to :
        /* some of this may look funky, but it's 'cause the linux driver and directx have
         * different opinions about which way direction "0" is.  directx has 0 along the y
         * axis (north/far, counteract), linux has it along the y axis (down). */
        if (dwFlags & DIEP_DIRECTION) {
            if (peff->cAxes == 1) {
                if (peff->dwFlags & DIEFF_CARTESIAN) {
                    if (peff->rgdwAxes[0] == DIJOFS_X && peff->rglDirection[0] >= 0)
                        This->effect.direction = 0x4000;
                    else if (peff->rgdwAxes[0] == DIJOFS_X && peff->rglDirection[0] < 0)
                        This->effect.direction = 0xC000;
                    else if (peff->rgdwAxes[0] == DIJOFS_Y && peff->rglDirection[0] >= 0)
                        This->effect.direction = 0x8000;
                    else if (peff->rgdwAxes[0] == DIJOFS_Y && peff->rglDirection[0] < 0)
                        This->effect.direction = 0;
    
    Change dinput/effect_linuxinput.c:148 :
        /* Major conversion factors are:
         * times: millisecond (linux) -> microsecond (windows) (x * 1000)
         * forces: scale 0x7FFF (linux) -> scale 10000 (windows) approx ((x / 33) * 10)
         * angles: scale 0x7FFF (linux) -> scale 35999 (windows) approx ((x / 33) * 36)
         * angle bases: 0 -> -y (down) (linux) -> 0 -> +x (right) (windows)
         */
    to :
        /* Major conversion factors are:
         * times: millisecond (linux) -> microsecond (windows) (x * 1000)
         * forces: scale 0x7FFF (linux) -> scale 10000 (windows) approx ((x / 33) * 10)
         * angles: scale 0xFFFF (linux) -> scale 35999 (windows) approx ((x / 66) * 36)
         * angle bases: 0 -> +y (down) (linux) -> 0 -> -y (far: counteract direction) (windows polar)
         */
    
    and change dinput/effect_linuxinput.c:184 :
        peff->rglDirection[0] = (This->effect.direction / 33) * 36 + 9000;
        if (peff->rglDirection[0] > 35999)
            peff->rglDirection[0] -= 35999;
    to :
        peff->rglDirection[0] = (This->effect.direction / 66) * 36;
    
    and change dinput/effect_linuxinput.c:415 :
        This->effect.direction = (int)((3 * M_PI / 2 - atan2(y, x)) * -0x7FFF / M_PI);
    to :
        This->effect.direction = (unsigned int)((M_PI / 2 + atan2(y, x)) * 0x8000 / M_PI);
    
    and change dinput/effect_linuxinput.c:505 :
        /* One condition block.  This needs to be rotated to direction,
         * and expanded to separate x and y conditions. */
        int i;
        double factor[2];
        factor[0] = asin((This->effect.direction * 3.0 * M_PI) / 0x7FFF);
        factor[1] = acos((This->effect.direction * 3.0 * M_PI) / 0x7FFF);
    to :
        /* One condition block.  This needs to be rotated to direction,
         * using dinput Cartesian coordinates, and expanded to separate x and y conditions. */
        int i;
        double factor[2];
        factor[0] = +sin((This->effect.direction * M_PI) / 0x8000);
        factor[1] = -cos((This->effect.direction * M_PI) / 0x8000);


b)  Applying direction seems to have no influence on X and Y force. (It's always polar 0 degrees) Tested in FEdit
    => Multiple things in the implementation are wrong:
        - typo in the bracket placement for the double-to-int conversion
        - forgetting (typo) that dinput works with 9000 instead of 90 (inconsistent with linux->dinput conversion of GetParameters)
        - common mistakes about linux directions, see above
        - it is more readable and accurate to use "* (0x8000 / 18000)" than "* (0xFFFF / 35999)",
        and wrapping of this (effect.direction) u16 is intentional.
    Solution: Change dinput/effect_linuxinput.c:419 :
        This->effect.direction = (int)(((double)peff->rglDirection[0] - 90) / 35999) * 0x7FFF;
    to :
        This->effect.direction = (unsigned int)(((double)peff->rglDirection[0] / 18000) * 0x8000);


c)  Multiple things are weird/wrong here:
    - common mistakes about linux directions, see above
    - "M_PI * 3", why '3' ???
    - Why "1000" as max magnitude of direction? type(rglDirection[i]) == 'LONG' => You can pick at least 10000 (DI_FFNOMINALMAX).
    So, change dinput/effect_linuxinput.c:174 :
        if (peff->dwFlags & DIEFF_CARTESIAN) {
            peff->rglDirection[0] = sin(M_PI * 3 * This->effect.direction / 0x7FFF) * 1000;
            peff->rglDirection[1] = cos(M_PI * 3 * This->effect.direction / 0x7FFF) * 1000;
    to :
        if (peff->dwFlags & DIEFF_CARTESIAN) {
            peff->rglDirection[0] = +sin(This->effect.direction * M_PI / 0x8000) * DI_FFNOMINALMAX;
            peff->rglDirection[1] = -cos(This->effect.direction * M_PI / 0x8000) * DI_FFNOMINALMAX;


d)  The statement "Polar and spherical are the same for 2 axes" is wrong:
        http://msdn.microsoft.com/en-us/library/windows/desktop/ee417536%28v=vs.85%29.aspx :
            "Polar coordinates are expressed as a single angle, in hundredths of a degree clockwise from whatever zero-point, or true north, has been established for the effect.
                Normally this is the negative y-axis; that is, away from the user."
            => (0, -1) direction is Polar starting direction (angle = 0)
        http://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.reference.dieffect%28v=vs.85%29.aspx :
            "If spherical, the first angle is measured in hundredths of a degree from the (1, 0) direction, rotated in the direction of (0, 1)."
            => (1, 0) direction is Spherical starting direction (angle = 0)
        (also take a look at the conversion table)
    
    So, change dinput/effect_linuxinput.c:177 :
        } else {
            /* Polar and spherical coordinates are the same for two or less
             * axes.
             * Note that we also use this case if NO flags are marked.
             * According to MSDN, we should return the direction in the
             * format that it was specified in, if no flags are marked.
             */
            peff->rglDirection[0] = (This->effect.direction / 66) * 36;
        }
    to :
        } else if (peff->dwFlags & DIEFF_SPHERICAL) {
            peff->rglDirection[0] = 27000 + (This->effect.direction / 66) * 36;
            if (peff->rglDirection[0] >= 36000)
                peff->rglDirection[0] -= 36000;
        } else /* if (peff->dwFlags & DIEFF_POLAR) */ {
            /* Note that we return polar coordinates if NO flags are marked.
             * According to MSDN, we should return the direction in the
             * format that it was specified in, if no flags are marked.
             * TODO: Return the direction in the format that it was specified in,
             *       if no flags are marked.
             */
            peff->rglDirection[0] = (This->effect.direction / 66) * 36;
        }
    
    And change dinput/effect_linuxinput.c:416 :
        } else {
            /* Polar and spherical are the same for 2 axes */
            /* Precision is important here, so we do double math with exact constants */
            This->effect.direction = (unsigned int)(((double)peff->rglDirection[0] / 18000) * 0x8000);
        }
    to :
        } else if (peff->dwFlags & DIEFF_SPHERICAL) {
            This->effect.direction = (unsigned int)(((9000 + (double)peff->rglDirection[0]) / 18000) * 0x8000);
        } else /* if (peff->dwFlags & DIEFF_POLAR) */ {
            This->effect.direction = (unsigned int)(((double)peff->rglDirection[0] / 18000) * 0x8000);
        }


e)  If "first_axis_is_x" in effect_linuxinput.c happens to be "false" and "cAxes == 2",
    doesn't the assignment of "effect.direction" in SetParameters(...) need additional transformations if "(peff->dwFlags & DIEFF_POLAR) != 0"?
    Because then x and y axis are swapped.
    With swapped x and y axes, then the following conversions hold:
    
        #Assume Possibility #1:
        #
        #          DInput         <->     Linux
        #    _________________     |   ___________
        #    Carts|Polar|Spher     |   Carts|Direc
        #    -----+-----+-----     |   -----+-----
        #    -1, 0|   0 | 270      |   -1, 0|  90 
        #    0, +1|  90 |   0      |   0, +1|   0 
        #    +1, 0| 180 |  90      |   +1, 0| 270 
        #    0, -1| 270 | 180      |   0, -1| 180 

        Assume Possibility #2:
            => Linux Cartesian coordinates are opposite in respect to the "Carts" reference.
            
                  DInput         <->     Linux
            _________________     |   ___________
            Carts|Polar|Spher     |   Carts|Direc
            -----+-----+-----     |   -----+-----
            -1, 0|   0 | 270      |   -1, 0| 270 
            0, +1|  90 |   0      |   0, +1| 180 
            +1, 0| 180 |  90      |   +1, 0|  90 
            0, -1| 270 | 180      |   0, -1|   0 

        (Possibility #2 is the correct one)

    So change dinput/effect_linuxinput.c:416 :
        } else if (peff->dwFlags & DIEFF_SPHERICAL) {
            This->effect.direction = (unsigned int)(((9000 + (double)peff->rglDirection[0]) / 18000) * 0x8000);
        } else /* if (peff->dwFlags & DIEFF_POLAR) */ {
            This->effect.direction = (unsigned int)(((double)peff->rglDirection[0] / 18000) * 0x8000);
        }
    to :
        } else if (peff->dwFlags & DIEFF_SPHERICAL) {
            This->effect.direction = (unsigned int)(((9000 + (double)peff->rglDirection[0]) / 18000) * 0x8000);
            if (!This->first_axis_is_x)
                This->effect.direction = 0xC000 - This->effect.direction;
        } else /* if (peff->dwFlags & DIEFF_POLAR) */ {
            This->effect.direction = (unsigned int)(((double)peff->rglDirection[0] / 18000) * 0x8000);
            if (!This->first_axis_is_x)
                This->effect.direction = 0xC000 - This->effect.direction;
        }
    
    Then we can simplify the following code of dinput/effect_linuxinput.c:405 :
        } else { /* two axes */
            if (peff->dwFlags & DIEFF_CARTESIAN) {
                LONG x, y;
                if (This->first_axis_is_x) {
                    x = peff->rglDirection[0];
                    y = peff->rglDirection[1];
                } else {
                    x = peff->rglDirection[1];
                    y = peff->rglDirection[0];
                }
                This->effect.direction = (unsigned int)((M_PI / 2 + atan2(y, x)) * 0x8000 / M_PI);
            } else if (peff->dwFlags & DIEFF_SPHERICAL) {
                This->effect.direction = (unsigned int)(((9000 + (double)peff->rglDirection[0]) / 18000) * 0x8000);
                if (!This->first_axis_is_x)
                    This->effect.direction = 0xC000 - This->effect.direction;
            } else /* if (peff->dwFlags & DIEFF_POLAR) */ {
                This->effect.direction = (unsigned int)(((double)peff->rglDirection[0] / 18000) * 0x8000);
                if (!This->first_axis_is_x)
                    This->effect.direction = 0xC000 - This->effect.direction;
            }
        }
    to :
        } else { /* two axes */
            if (peff->dwFlags & DIEFF_CARTESIAN) {
                This->effect.direction = (unsigned int)((M_PI / 2 + atan2(peff->rglDirection[1], peff->rglDirection[0])) * 0x8000 / M_PI);
            } else if (peff->dwFlags & DIEFF_SPHERICAL) {
                This->effect.direction = (unsigned int)(((9000 + (double)peff->rglDirection[0]) / 18000) * 0x8000);
            } else /* if (peff->dwFlags & DIEFF_POLAR) */ {
                This->effect.direction = (unsigned int)(((double)peff->rglDirection[0] / 18000) * 0x8000);
            }
            if (!This->first_axis_is_x)
                This->effect.direction = 0xC000 - This->effect.direction;
        }


f)  Condition effects (with struct-size of 2*sizeof(DICONDITION)) should keep "first_axis_is_x" into account:
    Change dinput/effect_linuxinput.c:520 :
        /* Two condition blocks.  Direct parameter copy. */
        int i;
        for (i = 0; i < 2; ++i) {
            This->effect.u.condition[i].center = (tsp[i].lOffset / 10) * 32;
            This->effect.u.condition[i].right_coeff = (tsp[i].lPositiveCoefficient / 10) * 32;
            This->effect.u.condition[i].left_coeff = (tsp[i].lNegativeCoefficient / 10) * 32;
            This->effect.u.condition[i].right_saturation = (tsp[i].dwPositiveSaturation / 10) * 65;
            This->effect.u.condition[i].left_saturation = (tsp[i].dwNegativeSaturation / 10) * 65;
            This->effect.u.condition[i].deadband = (tsp[i].lDeadBand / 10) * 65;
        }
    to :
        /* Two condition blocks.  Direct parameter copy, after a small change of axes if needed. */
        int i, j;
        for (i = 0; i < 2; ++i) {
            j = (first_axis_is_x ? i : 1-i);
            This->effect.u.condition[j].center = (tsp[i].lOffset / 10) * 32;
            This->effect.u.condition[j].right_coeff = (tsp[i].lPositiveCoefficient / 10) * 32;
            This->effect.u.condition[j].left_coeff = (tsp[i].lNegativeCoefficient / 10) * 32;
            This->effect.u.condition[j].right_saturation = (tsp[i].dwPositiveSaturation / 10) * 65;
            This->effect.u.condition[j].left_saturation = (tsp[i].dwNegativeSaturation / 10) * 65;
            This->effect.u.condition[j].deadband = (tsp[i].lDeadBand / 10) * 65;
        }
    
    The condition effects with struct-size of 1*sizeof(DICONDITION) don't need changes regarding "first_axis_is_x",
    because "factor[i]" depends on "effect.direction", which already incorporates "first_axis_is_x".


g)  Condition effects (with struct-size of 1*sizeof(DICONDITION)) are not transformed exactly right,
    look at "interactive.fig" for more understanding of the suggested code:
        - "deadband" can't be negative
        - for "left/right_coeff" and "left/right_saturation",
            if a direction along an axis is negative, "left" and "right" should be swapped
   
    Change dinput/effect_linuxinput.c:511 :
        for (i = 0; i < 2; ++i) {
            This->effect.u.condition[i].center = (int)(factor[i] * (tsp->lOffset / 10) * 32);
            This->effect.u.condition[i].right_coeff = (int)(factor[i] * (tsp->lPositiveCoefficient / 10) * 32);
            This->effect.u.condition[i].left_coeff = (int)(factor[i] * (tsp->lNegativeCoefficient / 10) * 32); 
            This->effect.u.condition[i].right_saturation = (int)(factor[i] * (tsp->dwPositiveSaturation / 10) * 65);
            This->effect.u.condition[i].left_saturation = (int)(factor[i] * (tsp->dwNegativeSaturation / 10) * 65);
            This->effect.u.condition[i].deadband = (int)(factor[i] * (tsp->lDeadBand / 10) * 65);
        }
    to :
        for (i = 0; i < 2; ++i) {
            This->effect.u.condition[i].center = (int)(factor[i] * (tsp->lOffset / 10) * 32);
            This->effect.u.condition[i].right_coeff = (int)(abs(factor[i]) * ((factor[i] >= 0 ? tsp->lPositiveCoefficient : tsp->lNegativeCoefficient) / 10) * 32);
            This->effect.u.condition[i].left_coeff = (int)(abs(factor[i]) * ((factor[i] >= 0 ? tsp->lNegativeCoefficient : tsp->lPositiveCoefficient) / 10) * 32);
            This->effect.u.condition[i].right_saturation = (int)(abs(factor[i]) * ((factor[i] >= 0 ? tsp->dwPositiveSaturation : tsp->dwNegativeSaturation) / 10) * 65);
            This->effect.u.condition[i].left_saturation = (int)(abs(factor[i]) * ((factor[i] >= 0 ? tsp->dwNegativeSaturation : tsp->dwPositiveSaturation) / 10) * 65);
            This->effect.u.condition[i].deadband = (int)(abs(factor[i]) * (tsp->lDeadBand / 10) * 65);
        }


More information about the wine-devel mailing list