3noix / UJPS

Universal Joystick Programing Software (C++)
GNU Lesser General Public License v3.0
16 stars 3 forks source link

Question: How to define "half S-curve" for raw input to merged axes? #23

Closed robert914 closed 6 years ago

robert914 commented 6 years ago

Now that the Xbox360 gamepad plugin has the left and right trigger axis split into two separate axes like they should be (thank you BTW!), I'm having trouble translating my S-curve into the proper half S-curves I need for each axis when I merge them.

I used to have this for the combined axis trigger (before the XInput changes):

    xbgp->setSCurve(XBGP::TRIGGER, 0.05f, 0.15f, 0.05f, 4.5f, 0.0f);
    MapAxis(xbgp, XBGP::TRIGGER, AllLayers, vjoy1, VJOY:: ROTZ);

Now, with the split axes, I'm doing this:

    xbgp->setSCurve(XBGP::TRIGGERL, 0.05f, 0.15f, 0.05f, 4.5f, 0.0f);
    xbgp->setSCurve(XBGP::TRIGGERR, 0.05f, 0.15f, 0.05f, 4.5f, 0.0f);
    MapMergeAxes(xbgp, XBGP::TRIGGERL, -0.5f, xbgp, XBGP::TRIGGERR, 0.5f, AllLayers, vjoy1, VJOY::ROTZ);

The axis works just fine, except with the above S-curve definition, it doesn't behave right. It does this weird fast moving on initial depression of the trigger, then slows half-way, and then speeds up again at the maximum depression. Which, since the S-curve is being applied to the full axis on one half (one trigger), it's actually doing what I told it to do. However, that's not what I want it to do. 👎

So, I pulled up the AxesCurves to see if I could figure out what to do, but even if I switch to setJCurve, that doesn't give me the dead zones, or the proper curve I want. Basically, I want to have half the S-curve applied to the trigger axis (i.e. I want to apply the 0-1 portion of the S-curve to the full trigger axis). So, it would look like an exponential style curve, starting at zero with some dead zone, then progressing slowly up initially, then increasing in slope towards the top, then maxing with a dead zone at the top. (I hope you can get what I'm going for from that description)

How do I accomplish this? I know I could just create a full custom point based curve, but I was wondering if there was a better way.

3noix commented 6 years ago

Ok, I understand the need. I will add a new S curve for non centered axes.

Moreover, I am not sure it is a good idea to define a setXxCurve for each curve. I am thinking about just letting the possibility to do "setCurve(new XxCurve{...}).

And I was also wondering: in this case (merged axes), wouldn't be better to apply the curve to the merged axis instead of the 2 original ones? It should be feasible easily if done inside the "MapMergeAxes" mapping.

robert914 commented 6 years ago

Yes, allowing application of a normal S-curve on the MapMergeAxes would work in this case just fine, and would likely be more desirable / easier.

I still feel we need a S-curve method for non-centered axis though (i.e. a one-sided S-curve with starting dead zone, curve, ending dead zone, and zoom). This would be useful for throttle / slider axes, which is what one of these triggers is like, a simple slider axis.

So I would request both options be available (for different application needs). Thanks!

3noix commented 6 years ago

Both done!

As I told above, I removed the EnhancedJoystick functions "setSCurve", "setJCurve" and "setCustomCurve", now you have to use the generic way: "myJoystick->setCurve(axisNumber, new MyCurve{})".

"JCurve" has been renamed as "polynomial 2nd order" and "SCurve" as "exponential centered".

The new curves are:

For MapMergeAxes, you just have to add a curve as the last argument (optional): MapMergeAxes(..., new MyCurve{}); For more details (and as long as the doc is not up-to-date) you can look at AbstractProfile modifications or my test profile.

robert914 commented 6 years ago

Excellent! I should be able to give this a try tonight.

3noix commented 6 years ago

I made an important change I forgot to speak about in the commit detailed comments: I change the "unit" of the deadzones for %. So you have to multiply your actual values by 100. All the curves should be in sync with what can be seen in the AxesCurves GUI.

Do you know if it is possible to edit the commit detailed comments?

robert914 commented 6 years ago

I don’t think there is a way to edit previous commit comments.

So you’re saying if I want 50%, I just use 50.0f instead of 0.5f as before? Right?

3noix commented 6 years ago

Yes, exactly. Note that this percentage is the fraction of the whole range [-1,1] that is used for the dead zone. If your left dead zone is 25%, you will obtain -1 as the output of the curve if the input is in [-1,-0.5].

robert914 commented 6 years ago

Ok, understood. Thanks!

robert914 commented 6 years ago

I'm a little confused. If I add the curve directly into MapMergeAxes, is that applying the curve to the merged axes (i.e. the output), or to each input axis? Your profile example doesn't make sense, so I can't tell. In your example, I see these lines:

    //mfgx->setCurve(MFGX::BRK_LEFT, new CurveExpNotCentered{0.0f,5.0f,3.0f,0.0f});
    //mfgx->setCurve(MFGX::BRK_RIGHT, new CurveExpNotCentered{0.0f,5.0f,3.0f,0.0f});
    MapMergeAxes(mfgx, MFGX::BRK_LEFT, 0.5f, mfgx, MFGX::BRK_RIGHT, -0.5f, AllLayers, vj1, VJOY::Y, new CurveExpNotCentered{0.0f,5.0f,3.0f,0.0f});

The fact that you have comments above, I assume that if I was to uncomment them, and remove the curve from the merge axes call, that I'd have different outputs based on the above. Was that your intent with the example?

Basically, if I have this:

    mfgx->setCurve(MFGX::BRK_LEFT, new CurveExpNotCentered{0.0f,5.0f,3.0f,0.0f});
    mfgx->setCurve(MFGX::BRK_RIGHT, new CurveExpNotCentered{0.0f,5.0f,3.0f,0.0f});
    MapMergeAxes(mfgx, MFGX::BRK_LEFT, 0.5f, mfgx, MFGX::BRK_RIGHT, -0.5f, AllLayers, vj1, VJOY::Y);

Is this the same as the above? (as shown in your example)

    MapMergeAxes(mfgx, MFGX::BRK_LEFT, 0.5f, mfgx, MFGX::BRK_RIGHT, -0.5f, AllLayers, vj1, VJOY::Y, new CurveExpNotCentered{0.0f,5.0f,3.0f,0.0f});

Or is this really what it should be to be the same as the first one:

    MapMergeAxes(mfgx, MFGX::BRK_LEFT, 0.5f, mfgx, MFGX::BRK_RIGHT, -0.5f, AllLayers, vj1, VJOY::Y, new CurveExpCentered{5.0f,0.0f,5.0f,3.0f,0.0f});

I believe this last line is actually the correct equivalent to the first, yes?

robert914 commented 6 years ago

I think the "curve" value is wrong on the ExpNonCentered. It should be exactly the same as the right half of the ExpCentered curve, but I have to cut the curve value in half again to make it the same.

If I have:

new CurveExpCentered{0.0f,0.0f,4.0f,0.0f}

and this:

new CurveExpNotCentered{0.0f,0.0f,4.0f,0.0f}

The curve of CurveExpNotCentered should have the same curvature as the positive 0-1 half of the CurveExpCentered curve, but it does not, it's more curved into the corner. Basically, I would expect the Y value of CurveExpNotCentered @ X=0.0 to be proportionally the same as the Y value of CurveExpCentered @ X=0.5, but it's not, it's lower relative to the curve.

I have to use this:

new CurveExpNotCentered{0.0f,0.0f,2.0f,0.0f}

To get the right curve Y value, I have to use the above (half the curve value). This doesn't seem right, but maybe I'm misunderstanding what your "curve" value means. Is this what you expected?

robert914 commented 6 years ago

It's more obvious if you use this:

new CurveExpCentered{0.0f,0.0f,10.0f,0.0f}

vs

new CurveExpNonCentered{0.0f,0.0f,10.0f,0.0f}

If you look at where the bend occurs in the centered, you see in 0-1 it bends just past 0.5. But in the non-centered one, between -1 and 1, it bends just past 0.5 also. That's not right, it should bend just past 0 on the non-centered curve (this is all viewing it from the AxesCurve.exe).

If you change the non-centered to this:

new CurveExpNonCentered{0.0f,0.0f,5.0f,0.0f}

Then the bend occurs just past 0 as I would expect.

Now, this could all be because one is 0 to 1 and the other is -1 to 1 (so the range of values is double), and this is the way you intend it to work. If so, I'm fine with that, I'm just making sure I'm using it correctly.

3noix commented 6 years ago

Indeed my test profile was not so clear. The first way is to apply a curve to the 2 inputs axes: y = k1 f(x1) + k2 f(x2)

mfgx->setCurve(MFGX::BRK_LEFT, new CurveExpNotCentered{0.0f,5.0f,3.0f,0.0f});
mfgx->setCurve(MFGX::BRK_RIGHT, new CurveExpNotCentered{0.0f,5.0f,3.0f,0.0f});
MapMergeAxes(mfgx, MFGX::BRK_LEFT, 0.5f, mfgx, MFGX::BRK_RIGHT, -0.5f, AllLayers, vj1, VJOY::Y);

The second one consists in applying the curve to the resulting axis: y = f(k1 x1 + k2 x2). You remove the 2 first lines and modify the 3rd one: MapMergeAxes(mfgx, MFGX::BRK_LEFT, 0.5f, mfgx, MFGX::BRK_RIGHT, -0.5f, AllLayers, vj1, VJOY::Y, new CurveExpNotCentered{0.0f,5.0f,3.0f,0.0f});

And indeed, in my example, it has no sense to use a non-centered (and highly curved) curve on a merged axis which has a center (it performs the difference between the 2 brake pedals), but it was easier for the test because in this case the difference between the 2 ways is obvious.

3noix commented 6 years ago

Ok, I understood the issue with the non-centered exp curve. I will modify it quietly this evening to avoid to do a new mistake.

3noix commented 6 years ago

I modified the curve equation of the non-centered version by dividing the curve parameter by 2 as you mentionned above. Thanks for your feedback!

3noix commented 6 years ago

But with the current exponential curves (and with the original Thrustmaster S curve), if you add deadzones, the curvature is changed: I would expect that the function is just compressed along the x axis. I will add another commit this week-end about that. So no difference is expected with no deadzone, and greater the deadzones greater the differences... I will provide some curves comparison, but it is not so important.

robert914 commented 6 years ago

Yes, the curvature will change with dead zones, that makes sense. However, the proportion of the range that is the dead zone should be correct for the same value of dead zone applied to either the centered or non-centered curves. For example, if you use an end dead zone of 10% on both a centered and non-centered curve, the centered curve should be flat at the end for 0.1 width (0.9 to 1.0), and it should flat at the end for 0.2 width (0.8 to 1.0) on the non-centered curve (i.e. the absolute length of the dead zone in the range would be double on the non-centered vs centered).

I believe that's what you are saying you need to still fix, and I agree.

3noix commented 6 years ago

Except the compression along the x axis, no, it does not make sense that the curvature changes. Let's take the example of the positive part of the exp centered curve. If there is no dead zone the expression is: y = (exp(c*x)-1) / (exp(c)-1)

Now let's add some deadzones: let's call m the minimal value of the interval (0 if no deadzone, >0 otherwise) and M the maximal value of the interval (1 if no deadzone, <1 otherwise). The expression used by the Thrustmaster S curve is: y = (exp(c(x-m))-1) / (exp(c(M-m))-1)

But if we don't want to deform the curve (and only compress it along the x axis), it should be: y = (exp(c*(x-m)/(M-m))-1) / (exp(c)-1), i.e. x is replaced by (x-m)/(M-m)

About the deadzone percentages, I was not telling anything about that. And it doesn't seem so intuitive to consider these percentages differently whether the curve is centered or not.

robert914 commented 6 years ago

Ok, I believe I understand what you mean now, and I agree. I’ll check it out once you’ve gotten it adjusted. Thanks!

3noix commented 6 years ago

I did some checks, and when deadzones are non-null, the "old curve" [i.e. y = (exp(c(x-m))-1) / (exp(c(M-m))-1)] is equivalent to the "new curve" [i.e. y = (exp(c*(x-m)/(M-m))-1) / (exp(c)-1)] if we apply a greater "curve parameter" to the old curve.

So someway it reduces the "x axis compression" effect induced by the addition of the deadzones. It may not be undesirable. curves_plots.zip matlab_sources.zip

robert914 commented 6 years ago

Which curve / equation are you proposing? Can you generate old/new curves without a dead zone for comparison? That might help determine what equation should be used.

3noix commented 6 years ago

The one I called "old" is the current one :-), and the "new" is my proposition. With no deadzone, old and new are the same. without_deadzones.zip

robert914 commented 6 years ago

Yes, I agree. Comparing the new dead zone version with the non-dead zone version, the new curve is more correct as the dead zones are added. I agree with the new curve equations. Thanks!

3noix commented 6 years ago

As you prefer it and as I am undecided, I did it in the last commit.