My problem: given x and y, I need to calculate x and y for the required deviation of the joystick.
It is simple when there is no dead zone of the joystick - I just use x and y without manipulation.
When there is a dead zone, I want x = 0 to be zero, and x = a non-zero value is the first value in this direction, which is outside the dead zone.
The square dead zone is simple. In the following code, x and y are from -1 to 1 inclusive. Dead zone from 0 to 1 inclusive.
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;
The circular dead zone is more complicated. After a number of mistakes, I came up with this:
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); }
Here's what happens to deflect the joystick at extreme values (deadzone = 0.25):
Deviation from the square deviation of the joystick. http://n4te.com/temp/nonsquare.gif
As you can see, the deviation does not apply to corners. IE, if x = 1, y = 1, then xDeflection and yDeflection are approximately 0.918. The problem worsens with large dead zones, making the green lines in the image above look more and more like a circle. In the dead zone = 1 green lines is the circle that corresponds to the dead zone.
I found that with a little change, I could increase the shape represented by the green lines and the clip values outside -1 to 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)); }
I came up with the constant 0.71 from trial and error. This number makes the shape large enough so that the angles are within a few decimal places from the actual angles. For academic reasons, can anyone explain why 0.71 turns out to be the number it does?
Overall, I'm not quite sure if I am finding the right approach. Is there a better way to accomplish what I need for a circular dead zone?
I wrote a simple Swing-based program for visual display:
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) {