ROS-Mobile / ROS-Mobile-Android

Visualization and controlling application for Android
464 stars 148 forks source link

add option to use full range (-1, 1) for joystick #87

Closed Kurtoid closed 1 year ago

Kurtoid commented 2 years ago

Adds a checkbox, "Use Realistic Stick Limits", to the joystick widget. It's checked by default, keeping the default behavior the same, where the joystick areas is a circle, and the corners can't be reached. When unchecked, the boundaries of the virtual joystick change to a square (or rectangle, if the width/height are not equal), making the corners reachable.

The new checkbox:

Joystick in square/rect mode:

Joystick in realistic mode:

Kurtoid commented 2 years ago

Two questions for the maintainers: 1) how is widget data stored? Do I need to do anything to save the checkbox state? 2) what do I need to do to make the stick re-draw itself when the checkbox changes? Right now, the stick only changes it's border once its touched, triggering a re-draw.

nicostudt commented 2 years ago

Two questions for the maintainers:

  1. how is widget data stored? Do I need to do anything to save the checkbox state?
  2. what do I need to do to make the stick re-draw itself when the checkbox changes? Right now, the stick only changes it's border once its touched, triggering a re-draw.

Thanks for your contribution!

To answer your questions:

  1. You did everything that was necessary. All variables in the JoystickEntity class or general WidgetEntity will automatically be saved to the storage and loaded from the last state.

  2. Since it is not updating on its own, you can try to force a redraw by calling "forceWidgetUpdate" from the ViewHolder class like in BatteryViewHolder.

nicostudt commented 2 years ago

Maybe two suggestions to discuss about the widget update:

  1. Instead of drawing the outer rect on the edge of the widget, I would suggest (same as the circle one) to draw the maximum collision boundary of the sticks center ... I hope you know what I mean.
  2. Rename the checkbox as I think the "realistic" may not be expressing the right thing.

What do you think?

Kurtoid commented 2 years ago

(sorry for the commit spam - should I squash it?) I changed it to "Use Rectangular Stick Limits" - but if it should be worded differently, go for it.

Thank you for the advice on re-drawing the widget. It looks like forceSetChecked in BatteryDetailVH (and now JoystickDetailVH) prevents some kind of update loop?

Right now, making the Joy widget rectangular (width != height) in rectangular mode causes some graphics weirdness. In the original circular mode, the circle diameter is constrained to min(height, width). Should I try fixing the stick boundary checks in that case, or just force the joystick area to be square instead?

nicostudt commented 2 years ago

It looks like forceSetChecked in BatteryDetailVH (and now JoystickDetailVH) prevents some kind of update loop? Yep, right!

I think its okay if its rectangular, so you have to limit the stick movement to your new boundary. Until now - good work!

nicostudt commented 2 years ago

To restrict the stick movement I would differentiate between circle and rectangular boundaries in the "pixel to whatever" conversions like this:

private float[] convertFromPxToRelative(float x, float y) {
    float middleX = getWidth() / 2f;
    float middleY = getHeight() / 2f;

    float[] relPos = new float[2];
    float dx = x - middleX;
    float dy = y - middleY;

    if (rectangular) {
        float maxW = middleX - joystickRadius;
        float maxH = middleY - joystickRadius;

        relPos[0] = Math.min(1, Math.max(-1, dx / maxW));
        relPos[1] = Math.min(1, Math.max(-1, -dy / maxH));

    } else {
        float r = middleX - joystickRadius;
        double rad = Math.atan2(dy, dx);

        double len = Math.sqrt(dx * dx + dy * dy) / r;
        len = Math.min(1, len);

        relPos[0] = (float) (Math.cos(rad) * len);
        relPos[1] = (float) (-Math.sin(rad) * len);
    }

    return relPos;
}

and the other way around:

private float[] convertFromRelativeToPx(float x, float y) {
    float middleX = getWidth() / 2f;
    float middleY = getHeight() / 2f;
    float[] px = new float[2];

    if (rectangular) {
        float maxW = middleX - joystickRadius;
        float maxH = middleY - joystickRadius;
        px[0] = middleX + x * maxW;
        px[1] = middleY - y * maxH;

    } else {
        float r = middleX - joystickRadius;
        px[0] = middleX + x * r;
        px[1] = middleY - y * r;
    }

    return px;
}