Java 操纵杆死区计算
我的问题是:给定x和y,我需要计算所需操纵杆偏转的x和y 这在没有操纵杆死区的情况下很简单——我只使用x和y,不需要操纵 当存在死区时,我希望x=0为零,x=non-zero为该方向上死区外的第一个值 方形死区很简单。在下面的代码中,x和y的值介于-1和1之间。死区范围为0到1(含0到1)Java 操纵杆死区计算,java,trigonometry,Java,Trigonometry,我的问题是:给定x和y,我需要计算所需操纵杆偏转的x和y 这在没有操纵杆死区的情况下很简单——我只使用x和y,不需要操纵 当存在死区时,我希望x=0为零,x=non-zero为该方向上死区外的第一个值 方形死区很简单。在下面的代码中,x和y的值介于-1和1之间。死区范围为0到1(含0到1) float xDeflection = 0; if (x > 0) xDeflection = (1 - deadzone) * x + deadzone; else if (x < 0) x
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;
以下是极端情况下(死区=0.25)操纵手柄偏转的输出:
如您所见,偏转不会延伸到拐角处。也就是说,如果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();
}
}
导入java.awt.BorderLayout;
导入java.awt.CardLayout;
导入java.awt.Color;
导入java.awt.Dimension;
导入java.awt.Graphics;
导入java.awt.event.ActionEvent;
导入java.awt.event.ActionListener;
导入java.util.Hashtable;
导入javax.swing.DefaultComboxModel;
导入javax.swing.JComboBox;
导入javax.swing.JFrame;
导入javax.swing.JLabel;
导入javax.swing.JPanel;
导入javax.swing.JSlider;
导入javax.swing.event.ChangeEvent;
导入javax.swing.event.ChangeListener;
公共类DeadzoneTest扩展JFrame{
浮动X状态,Y状态;
浮动死区=0.3f;
int size=(int)(255*死区);
公共区域测试(){
超级(“死区测试”);
setDefaultCloseOperation(在关闭时处理);
最终CardLayout CardLayout=新的CardLayout();
最终JPanel中心面板=新JPanel(cardLayout);
getContentPane().add(中心面板,BorderLayout.CENTER);
centerPanel.setPreferredSize(新尺寸(512、512));
Hashtable labels=新的Hashtable();
标签.put(-255,新的JLabel(“-1”);
标签.put(-128,新JLabel(“-0.5”);
标签。放置(0,新标签(“0”);
标签.put(128,新标签(“0.5”);
标签。放置(255个,新标签(“1”);
最终JSlider ySlider=新JSlider(JSlider.VERTICAL,-256,256,0);
getContentPane().add(ySlider,BorderLayout.EAST);
ySlider.setinversed(真);
ySlider.可设置标签(标签);
ySlider.setPaintLabels(真);
ySlider.setMajorTickSpacing(32);
ySlider.setSnapToTicks(真);
ySlider.addChangeListener(新的ChangeListener(){
公共无效状态已更改(ChangeEvent事件){
yState=ySlider.getValue()/255f;
centerPanel.repaint();
}
});
final JSlider xSlider=新的JSlider(JSlider.HORIZONTAL,-256,256,0);
getContentPane().add(xSlider,BorderLayout.SOUTH);
xSlider.setLabelTable(标签);
xSlider.setPaintLabels(true);
xSlider.setMajorTickSpacing(32);
xSlider.setSnapToTicks(true);
addChangeListener(新的ChangeListener(){
公共无效状态已更改(ChangeEvent事件){
xState=xSlider.getValue()/255f;
centerPanel.repaint();
}
});
最终JSlider死区SLIDER=新JSlider(JSlider.VERTICAL,0,100,33);
getContentPane().add(deadzoneSlider,BorderLayout.WEST);
死区滑动器设置反转(真);
死区滑动器createStandardLabels(25);
死区滑块。设置油漆标签(真);
死区滑道设置主滑道间距(25);
死区滑动器设定值(真);
deadzoneSlider.addChangeListener(新的ChangeListener(){
公共无效状态已更改(ChangeEvent事件){
deadzone=deadzoneSlider.getValue()/100f;
大小=(整数)(255*死区);
centerPanel.repaint();
}
});
最终JComboBox组合=新JComboBox();
setModel(新的DefaultComboxModel(新对象[]{“round”,“square”}));
getContentPane().add(组合,BorderLayout.NORTH);
combo.addActionListener(新ActionListener(){
已执行的公共无效操作(操作事件){
显示(中心面板,(字符串)组合.getSelectedItem());
}
});
centerPanel.add(新面板(){
公共无效到偏移(图形g、浮点x、浮点y){
g、 drawRect(256大小,256大小,大小*2,大小*2);
浮点xDeflection=0;
如果(x>0)
xDeflection=(1-死区)*x+死区;
else如果(x<0){
xDeflection=(1-死区)*x-死区;
}
浮动折射率=0;
如果(y>0)
y反射=(1-死区)*y+死区;
else if(y<0){
y反射=(1-死区)*y-死区;
}
绘制(g、X反射、Y反射);
}
},“正方形”);
centerPanel.add(新面板(){
公共无效到偏移(图形g、浮点x、浮点y){
g、 drawOval(256号,256号,2号,2号);
浮点xDeflection=0,yDeflection=0;
如果(x!=0 | | y!=0){
浮动分布=1-0.71f*死区;
浮动距离=分布*(浮动)数学sqrt(x*x+y*y)+死区;
双角度=数学atan2(x,y);
xDeflection=dist*(浮点)数学sin(角度);
xDeflection=Math.min(1,Math.max(-1,xDeflection));
yDeflection=dist*(浮点)数学cos(角度);
yDeflection=Math.min(1,Math.max(-1,yDeflection));
}
绘制(g、X反射、Y反射);
}
}“圆形”);
显示(中心面板,(字符串)组合.getSelectedItem());
包装();
setLocationRelativeTo(空);
setVisible(真);
}
私有抽象类面板扩展了JPanel{
公共组件(图形g){
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();
}
}
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;
}
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);
}
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);
}