расчет мертвой зоны джойстика - PullRequest
4 голосов
/ 22 декабря 2009

Моя проблема: учитывая x и y, мне нужно вычислить x и y для требуемого отклонения джойстика.

Это просто, когда нет мертвой зоны джойстика - я просто использую x и y без манипуляций.

Когда есть мертвая зона, я хочу, чтобы x = 0 был нулем, а x = ненулевое - первым значением в этом направлении, которое находится за пределами мертвой зоны.

Квадратная мертвая зона проста. В следующем коде x и y имеют значение от -1 до 1 включительно. Зона нечувствительности от 0 до 1 включительно.

float xDeflection = 0;
if (x > 0)
 xDeflection = (1 - deadzone) * x + deadzone;
else if (x < 0)
 xDeflection = (1 - deadzone) * x - deadzone;

float yDeflection = 0;
if (y > 0)
 yDeflection = (1 - deadzone) * y + deadzone;
else if (y < 0)
 yDeflection = (1 - deadzone) * y - deadzone;

Круглая мертвая зона сложнее. После большого количества дурачения я придумал это:

float xDeflection = 0, yDeflection = 0;
if (x != 0 || y != 0) {
 float distRange = 1 - deadzone;
 float dist = distRange * (float)Math.sqrt(x * x + y * y) + deadzone;
 double angle = Math.atan2(x, y);
 xDeflection = dist * (float)Math.sin(angle);
 yDeflection = dist * (float)Math.cos(angle);
}

Вот что это выводит для отклонения джойстика в крайних точках (мертвая зона = 0,25):

Неквадратичное отклонение джойстика. http://n4te.com/temp/nonsquare.gif

Как видите, прогиб не распространяется на углы. То есть, если x = 1, y = 1, то xDeflection и yDeflection равны 0,918. Проблема обостряется с большими мертвыми зонами, из-за чего зеленые линии на изображении выше все больше напоминают круг. В мертвой зоне = 1 зеленые линии - это круг, соответствующий мертвой зоне.

Я обнаружил, что с небольшим изменением я мог бы увеличить форму, представленную зелеными линиями и значениями обрезки, от -1 до 1:

if (x != 0 || y != 0) {
 float distRange = 1 - 0.71f * deadzone;
 float dist = distRange * (float)Math.sqrt(x * x + y * y) + deadzone;
 double angle = Math.atan2(x, y);
 xDeflection = dist * (float)Math.sin(angle);
 xDeflection = Math.min(1, Math.max(-1, xDeflection));
 yDeflection = dist * (float)Math.cos(angle);
 yDeflection = Math.min(1, Math.max(-1, yDeflection));
}

Я придумал постоянную 0,71 методом проб и ошибок. Это число делает форму достаточно большой, чтобы углы находились в нескольких десятичных разрядах от фактических углов. По академическим причинам, кто-нибудь может объяснить, почему 0,71 является числом, которое делает это?

В целом, я не совсем уверен, правильно ли я выбрал подход. Есть ли лучший способ сделать то, что мне нужно для круглой мертвой зоны?

Я написал простую программу на основе Swing для визуализации происходящего:

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Hashtable;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class DeadzoneTest extends JFrame {
 float xState, yState;
 float deadzone = 0.3f;
 int size = (int)(255 * deadzone);

 public DeadzoneTest () {
  super("DeadzoneTest");
  setDefaultCloseOperation(DISPOSE_ON_CLOSE);

  final CardLayout cardLayout = new CardLayout();
  final JPanel centerPanel = new JPanel(cardLayout);
  getContentPane().add(centerPanel, BorderLayout.CENTER);
  centerPanel.setPreferredSize(new Dimension(512, 512));

  Hashtable labels = new Hashtable();
  labels.put(-255, new JLabel("-1"));
  labels.put(-128, new JLabel("-0.5"));
  labels.put(0, new JLabel("0"));
  labels.put(128, new JLabel("0.5"));
  labels.put(255, new JLabel("1"));

  final JSlider ySlider = new JSlider(JSlider.VERTICAL, -256, 256, 0);
  getContentPane().add(ySlider, BorderLayout.EAST);
  ySlider.setInverted(true);
  ySlider.setLabelTable(labels);
  ySlider.setPaintLabels(true);
  ySlider.setMajorTickSpacing(32);
  ySlider.setSnapToTicks(true);
  ySlider.addChangeListener(new ChangeListener() {
   public void stateChanged (ChangeEvent event) {
    yState = ySlider.getValue() / 255f;
    centerPanel.repaint();
   }
  });

  final JSlider xSlider = new JSlider(JSlider.HORIZONTAL, -256, 256, 0);
  getContentPane().add(xSlider, BorderLayout.SOUTH);
  xSlider.setLabelTable(labels);
  xSlider.setPaintLabels(true);
  xSlider.setMajorTickSpacing(32);
  xSlider.setSnapToTicks(true);
  xSlider.addChangeListener(new ChangeListener() {
   public void stateChanged (ChangeEvent event) {
    xState = xSlider.getValue() / 255f;
    centerPanel.repaint();
   }
  });

  final JSlider deadzoneSlider = new JSlider(JSlider.VERTICAL, 0, 100, 33);
  getContentPane().add(deadzoneSlider, BorderLayout.WEST);
  deadzoneSlider.setInverted(true);
  deadzoneSlider.createStandardLabels(25);
  deadzoneSlider.setPaintLabels(true);
  deadzoneSlider.setMajorTickSpacing(25);
  deadzoneSlider.setSnapToTicks(true);
  deadzoneSlider.addChangeListener(new ChangeListener() {
   public void stateChanged (ChangeEvent event) {
    deadzone = deadzoneSlider.getValue() / 100f;
    size = (int)(255 * deadzone);
    centerPanel.repaint();
   }
  });

  final JComboBox combo = new JComboBox();
  combo.setModel(new DefaultComboBoxModel(new Object[] {"round", "square"}));
  getContentPane().add(combo, BorderLayout.NORTH);
  combo.addActionListener(new ActionListener() {
   public void actionPerformed (ActionEvent event) {
    cardLayout.show(centerPanel, (String)combo.getSelectedItem());
   }
  });

  centerPanel.add(new Panel() {
   public void toDeflection (Graphics g, float x, float y) {
    g.drawRect(256 - size, 256 - size, size * 2, size * 2);
    float xDeflection = 0;
    if (x > 0)
     xDeflection = (1 - deadzone) * x + deadzone;
    else if (x < 0) {
     xDeflection = (1 - deadzone) * x - deadzone;
    }
    float yDeflection = 0;
    if (y > 0)
     yDeflection = (1 - deadzone) * y + deadzone;
    else if (y < 0) {
     yDeflection = (1 - deadzone) * y - deadzone;
    }
    draw(g, xDeflection, yDeflection);
   }
  }, "square");

  centerPanel.add(new Panel() {
   public void toDeflection (Graphics g, float x, float y) {
    g.drawOval(256 - size, 256 - size, size * 2, size * 2);
    float xDeflection = 0, yDeflection = 0;
    if (x != 0 || y != 0) {
     float distRange = 1 - 0.71f * deadzone;
     float dist = distRange * (float)Math.sqrt(x * x + y * y) + deadzone;
     double angle = Math.atan2(x, y);
     xDeflection = dist * (float)Math.sin(angle);
     xDeflection = Math.min(1, Math.max(-1, xDeflection));
     yDeflection = dist * (float)Math.cos(angle);
     yDeflection = Math.min(1, Math.max(-1, yDeflection));
    }
    draw(g, xDeflection, yDeflection);
   }
  }, "round");

  cardLayout.show(centerPanel, (String)combo.getSelectedItem());
  pack();
  setLocationRelativeTo(null);
  setVisible(true);
 }

 private abstract class Panel extends JPanel {
  public void paintComponent (Graphics g) {
   g.setColor(Color.gray);
   g.fillRect(0, 0, getWidth(), getHeight());
   g.setColor(Color.white);
   g.fillRect(0, 0, 512, 512);

   g.setColor(Color.green);
   if (true) {
    // Draws all edge points.
    for (int i = -255; i < 256; i++)
     toDeflection(g, i / 255f, 1);
    for (int i = -255; i < 256; i++)
     toDeflection(g, i / 255f, -1);
    for (int i = -255; i < 256; i++)
     toDeflection(g, 1, i / 255f);
    for (int i = -255; i < 256; i++)
     toDeflection(g, -1, i / 255f);
   } else if (false) {
    // Draws all possible points (slow).
    for (int x = -255; x < 256; x++)
     for (int y = -255; y < 256; y++)
      toDeflection(g, x / 255f, y / 255f);
   }

   g.setColor(Color.red);
   toDeflection(g, xState, yState);
  }

  abstract public void toDeflection (Graphics g, float x, float y);

  public void draw (Graphics g, float xDeflection, float yDeflection) {
   int r = 5, d = r * 2;
   g.fillRect((int)(xDeflection * 256) + 256 - r, (int)(yDeflection * 256) + 256 - r, d, d);
  }
 }

 public static void main (String[] args) {
  new DeadzoneTest();
 }
}

Ответы [ 3 ]

4 голосов
/ 22 декабря 2009

Если у вас круговая мертвая зона, то 0,71 на самом деле 0,70710678 или половина квадратного корня из 2 Расчет благодаря теореме Пифагора

3 голосов
/ 22 декабря 2009

Это то, что я бросил вместе. Это ведет себя немного странно, но в границах это хорошо:

private Point2D.Float calculateDeflection(float x, float y) {
    Point2D.Float center = new Point2D.Float(0, 0);
    Point2D.Float joyPoint = new Point2D.Float(x, y);
    Double angleRad = Math.atan2(y, x);

    float maxDist = getMaxDist(joyPoint);

    float factor = (maxDist - deadzone) / maxDist;

    Point2D.Float factoredPoint = new Point2D.Float(x * factor, y * factor);

    float factoredDist = (float) center.distance(factoredPoint);

    float finalDist = factoredDist + deadzone;

    float finalX = finalDist * (float) Math.cos(angleRad);
    float finalY = finalDist * (float) Math.sin(angleRad);

    Point2D.Float finalPoint = new Point2D.Float(finalX, finalY);

    return finalPoint;
}

Редактировать: пропустил этот.

private float getMaxDist(Point2D.Float point) {
    float xMax;
    float yMax;
    if (Math.abs(point.x) > Math.abs(point.y)) {
        xMax = Math.signum(point.x);
        yMax = point.y * point.x / xMax;
    } else {
        yMax = Math.signum(point.y);
        xMax = point.x * point.y / yMax;
    }
    Point2D.Float maxPoint = new Point2D.Float(xMax, yMax);
    Point2D.Float center = new Point2D.Float(0, 0);
    return (float) center.distance(maxPoint);
}

Сохраняет угол, но масштабирует расстояние от 0 до границы между мертвой зоной и границей. Максимальное расстояние варьируется, поскольку оно равно 1 по бокам и sqrt (2) по углам, поэтому масштабирование должно быть изменено соответствующим образом.

3 голосов
/ 22 декабря 2009

Я бы попытался решить проблему немного по-другому. Как я понял ваши требования, алгоритм должен

  1. возвращает значения x / y, если положение джойстика находится за пределами мертвой зоны
  2. вернуть 0 / y, x / 0 или 0/0, если джойстик (частично) находится внутри мертвой зоны

Скажем, джойстик нажат, но x находится внутри заданной горизонтальной мертвой зоны, вам нужна координата (0, y).

Итак, на первом шаге я бы проверил, находятся ли координаты джойстика внутри заданной мертвой зоны. Для круга это довольно просто, вам просто нужно преобразовать координаты x / y в расстояние ( Пифагор ) и проверить, меньше ли это расстояние, чем радиус круга.

Если он снаружи, вернитесь (x / y). Если он внутри, проверьте наличие х и значения внутри их горизонтальной или вертикальной мертвой зоны.

Вот черновик, излагающий мою идею:

private Point convertRawJoystickCoordinates(int x, int y, double deadzoneRadius) {

    Point result = new Point(x,y); // a class with just two members, int x and int y
    boolean isInDeadzone = testIfRawCoordinatesAreInDeadzone(x,y,radius);
    if (isInDeadzone) {
      result.setX(0);
      result.setY(0);
    } else {
      if (Math.abs((double) x) < deadzoneRadius) {
        result.setX(0);
      }
      if (Math.abs((double) y) < deadzoneRadius) {
        result.setY(0);
      }
    }
    return result;         
}

private testIfRawCoordinatesAreInDeadzone(int x, int y, double radius) {
  double distance = Math.sqrt((double)(x*x)+(double)(y*y));
  return distance < radius;
}

Редактировать

Приведенная выше идея использует необработанные координаты, поэтому предположим, что диапазон необработанных значений x равен [-255,255], радиус равен 2, и вы установите джойстик на значения x (-3, -2, -1,0,1, 2,3), он будет производить последовательность (-3,0,0,0,0,0,3). Таким образом, мертвая зона закрыта, но есть скачок от 0 до 3. Если это нежелательно, мы можем «растянуть» не мертвую зону с ([-256, -radius], [radius, 256]) до (нормализовано) диапазон ([-1,0], [0,1]).

Так что мне просто нужно нормализовать конвертированные исходные точки:

private Point normalize(Point p, double radius) {
   double validRangeX = MAX_X - radius;
   double validRangeY = MAX_Y - radius;
   double x = (double) p.getX();
   double y = (double) p.getY();

   return new Point((x-r)/validXRange, (y-r)/validYRange);
}

Вкратце: он нормализует допустимые диапазоны (диапазон минус радиус мертвой зоны) для осей x и y до [-1,1], так что raw_x = radius преобразуется в normalized_x = 0.

(метод должен работать для положительных и отрицательных значений. По крайней мере, я на это надеюсь, у меня нет IDE или JDK под рукой для проверки;))

...