Ellipse2D 绘制精度差
Ellipse2D draws with poor accuracy
我正在做一个关于 space 物理学的应用程序,所以我做了很多关于轨道的工作。自然是遇到Ellipse2D.Double
在屏幕上画我的轨道
每当我的 JPanel 刷新时,我都会使用 Ellipse2D 绘制 body 的轨道,并使用不同的方法绘制 body 本身。
基本上,我发现当数字变得非常大时(无论是轨道的大小变大还是可视化被放大得很远),body 和 Ellipse2D 的位置不会排队。
我使用从极坐标到直角坐标的转换来计算 body 的位置,我将 Ellipse2D 的数学计算留给 geom
包。
看看这个代码示例。这是我能做的最多 self-contained 版本的问题,因为圆的比例必须非常大:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.math.BigDecimal;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class EllipseDemo extends JPanel {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.add(new EllipseDemo());
frame.setVisible(true);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// These values allow for a very zoomed in view of a piece of the circle
BigDecimal[] circleCenter = { new BigDecimal(-262842.5), new BigDecimal(-93212.8) };
BigDecimal circleRadius = new BigDecimal(279081.3);
// Draw the circle at the given center, with the given width and height
// x = centerx - radius, y = centery - radius, w = h = radius * 2
g2d.draw(new Ellipse2D.Double(circleCenter[0].subtract(circleRadius).doubleValue(),
circleCenter[1].subtract(circleRadius).doubleValue(), circleRadius.multiply(new BigDecimal(2)).doubleValue(),
circleRadius.multiply(new BigDecimal(2)).doubleValue()));
// Get a rectangular conversion of a point on the circle at this angle
BigDecimal angle = new BigDecimal(0.34117696217);
BigDecimal[] rectangular = convertPolarToRectangular(new BigDecimal[] {
circleRadius, angle });
// Draw a line from the center of the circle to the point
g2d.draw(new Line2D.Double(circleCenter[0].doubleValue(), circleCenter[1].doubleValue(),
circleCenter[0].add(rectangular[0]).doubleValue(), circleCenter[1]
.add(rectangular[1]).doubleValue()));
}
public BigDecimal[] convertPolarToRectangular(BigDecimal[] polar) {
BigDecimal radius = polar[0];
BigDecimal angle = polar[1];
BigDecimal x = radius.multiply(new BigDecimal(Math.cos(angle.doubleValue())));
BigDecimal y = radius.multiply(new BigDecimal(Math.sin(angle.doubleValue())));
return new BigDecimal[] { x, y };
}
}
上面的代码本质上是在屏幕上很远的地方画了一个大半径的圆。我选择了尺寸,以便在小 window 中可以看到一小块圆。
然后它从圆心到 window 中可见的圆上的点绘制一条线:我选择了一个在 window 上可见的角度并使用几何来将该角度和圆的半径转换为直角坐标。
这是程序显示的内容:
请注意,该直线实际上并未最终接触到椭圆。现在,我决定必须查明是我计算的点还是椭圆不正确。我用计算器算了一下,发现直线是对的,椭圆不对:
考虑到计算器可能没有错误,我相信Ellipse2D 绘制不正确。但是,我尝试了很多其他角度,这是我发现的模式:
而且 让我相信计算有问题。
这就是我的问题。我应该使用 Ellipse2D 以外的东西吗?也许 Ellipse2D 不够准确?我在我的代码示例中使用了 BigDecimals,因为我认为它会给我更高的精度——这是错误的方法吗?我的最终目标是能够计算出特定角度的椭圆上一点的矩形位置。
提前致谢。
您看到此错误是因为 Ellipse2D 由四个三次曲线近似。为了确保只看一下定义形状边界的路径迭代器:http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/awt/geom/EllipseIterator.java#187
为了提高质量,我们应该通过更多的三次曲线来近似椭圆。这是标准 java 实现的扩展,具有可变的段数:
class BetterEllipse extends Ellipse2D.Double {
private int segments;
public BetterEllipse(int segments, double x, double y, double w, double h) {
super(x, y, w, h);
this.segments = segments;
}
public int getSegments() {
return segments;
}
@Override
public PathIterator getPathIterator(final AffineTransform affine) {
return new PathIterator() {
private int index = 0;
@Override
public void next() {
index++;
}
@Override
public int getWindingRule() {
return WIND_NON_ZERO;
}
@Override
public boolean isDone() {
return index > getSegments() + 1;
}
@Override
public int currentSegment(double[] coords) {
int count = getSegments();
if (index > count)
return SEG_CLOSE;
BetterEllipse ellipse = BetterEllipse.this;
double x = ellipse.getCenterX() + Math.sin(2 * Math.PI * index / count) * ellipse.getWidth() / 2;
double y = ellipse.getCenterY() + Math.cos(2 * Math.PI * index / count) * ellipse.getHeight() / 2;
if (index == 0) {
coords[0] = x;
coords[1] = y;
if (affine != null)
affine.transform(coords, 0, coords, 0, 1);
return SEG_MOVETO;
}
double x0 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index - 2) / count) * ellipse.getWidth() / 2;
double y0 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index - 2) / count) * ellipse.getHeight() / 2;
double x1 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index - 1) / count) * ellipse.getWidth() / 2;
double y1 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index - 1) / count) * ellipse.getHeight() / 2;
double x2 = x;
double y2 = y;
double x3 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index + 1) / count) * ellipse.getWidth() / 2;
double y3 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index + 1) / count) * ellipse.getHeight() / 2;
double x1ctrl = x1 + (x2 - x0) / 6;
double y1ctrl = y1 + (y2 - y0) / 6;
double x2ctrl = x2 + (x1 - x3) / 6;
double y2ctrl = y2 + (y1 - y3) / 6;
coords[0] = x1ctrl;
coords[1] = y1ctrl;
coords[2] = x2ctrl;
coords[3] = y2ctrl;
coords[4] = x2;
coords[5] = y2;
if (affine != null)
affine.transform(coords, 0, coords, 0, 3);
return SEG_CUBICTO;
}
@Override
public int currentSegment(float[] coords) {
double[] temp = new double[6];
int ret = currentSegment(temp);
for (int i = 0; i < coords.length; i++)
coords[i] = (float)temp[i];
return ret;
}
};
}
}
下面是如何在代码中使用它而不是标准代码(我在这里使用 100 个片段):
g2d.draw(new BetterEllipse(100, circleCenter[0].subtract(circleRadius).doubleValue(),
circleCenter[1].subtract(circleRadius).doubleValue(), circleRadius.multiply(new BigDecimal(2)).doubleValue(),
circleRadius.multiply(new BigDecimal(2)).doubleValue()));
我正在做一个关于 space 物理学的应用程序,所以我做了很多关于轨道的工作。自然是遇到Ellipse2D.Double
在屏幕上画我的轨道
每当我的 JPanel 刷新时,我都会使用 Ellipse2D 绘制 body 的轨道,并使用不同的方法绘制 body 本身。
基本上,我发现当数字变得非常大时(无论是轨道的大小变大还是可视化被放大得很远),body 和 Ellipse2D 的位置不会排队。
我使用从极坐标到直角坐标的转换来计算 body 的位置,我将 Ellipse2D 的数学计算留给 geom
包。
看看这个代码示例。这是我能做的最多 self-contained 版本的问题,因为圆的比例必须非常大:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.math.BigDecimal;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class EllipseDemo extends JPanel {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.add(new EllipseDemo());
frame.setVisible(true);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// These values allow for a very zoomed in view of a piece of the circle
BigDecimal[] circleCenter = { new BigDecimal(-262842.5), new BigDecimal(-93212.8) };
BigDecimal circleRadius = new BigDecimal(279081.3);
// Draw the circle at the given center, with the given width and height
// x = centerx - radius, y = centery - radius, w = h = radius * 2
g2d.draw(new Ellipse2D.Double(circleCenter[0].subtract(circleRadius).doubleValue(),
circleCenter[1].subtract(circleRadius).doubleValue(), circleRadius.multiply(new BigDecimal(2)).doubleValue(),
circleRadius.multiply(new BigDecimal(2)).doubleValue()));
// Get a rectangular conversion of a point on the circle at this angle
BigDecimal angle = new BigDecimal(0.34117696217);
BigDecimal[] rectangular = convertPolarToRectangular(new BigDecimal[] {
circleRadius, angle });
// Draw a line from the center of the circle to the point
g2d.draw(new Line2D.Double(circleCenter[0].doubleValue(), circleCenter[1].doubleValue(),
circleCenter[0].add(rectangular[0]).doubleValue(), circleCenter[1]
.add(rectangular[1]).doubleValue()));
}
public BigDecimal[] convertPolarToRectangular(BigDecimal[] polar) {
BigDecimal radius = polar[0];
BigDecimal angle = polar[1];
BigDecimal x = radius.multiply(new BigDecimal(Math.cos(angle.doubleValue())));
BigDecimal y = radius.multiply(new BigDecimal(Math.sin(angle.doubleValue())));
return new BigDecimal[] { x, y };
}
}
上面的代码本质上是在屏幕上很远的地方画了一个大半径的圆。我选择了尺寸,以便在小 window 中可以看到一小块圆。
然后它从圆心到 window 中可见的圆上的点绘制一条线:我选择了一个在 window 上可见的角度并使用几何来将该角度和圆的半径转换为直角坐标。
这是程序显示的内容:
请注意,该直线实际上并未最终接触到椭圆。现在,我决定必须查明是我计算的点还是椭圆不正确。我用计算器算了一下,发现直线是对的,椭圆不对:
考虑到计算器可能没有错误,我相信Ellipse2D 绘制不正确。但是,我尝试了很多其他角度,这是我发现的模式:
而且 让我相信计算有问题。
这就是我的问题。我应该使用 Ellipse2D 以外的东西吗?也许 Ellipse2D 不够准确?我在我的代码示例中使用了 BigDecimals,因为我认为它会给我更高的精度——这是错误的方法吗?我的最终目标是能够计算出特定角度的椭圆上一点的矩形位置。
提前致谢。
您看到此错误是因为 Ellipse2D 由四个三次曲线近似。为了确保只看一下定义形状边界的路径迭代器:http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/awt/geom/EllipseIterator.java#187
为了提高质量,我们应该通过更多的三次曲线来近似椭圆。这是标准 java 实现的扩展,具有可变的段数:
class BetterEllipse extends Ellipse2D.Double {
private int segments;
public BetterEllipse(int segments, double x, double y, double w, double h) {
super(x, y, w, h);
this.segments = segments;
}
public int getSegments() {
return segments;
}
@Override
public PathIterator getPathIterator(final AffineTransform affine) {
return new PathIterator() {
private int index = 0;
@Override
public void next() {
index++;
}
@Override
public int getWindingRule() {
return WIND_NON_ZERO;
}
@Override
public boolean isDone() {
return index > getSegments() + 1;
}
@Override
public int currentSegment(double[] coords) {
int count = getSegments();
if (index > count)
return SEG_CLOSE;
BetterEllipse ellipse = BetterEllipse.this;
double x = ellipse.getCenterX() + Math.sin(2 * Math.PI * index / count) * ellipse.getWidth() / 2;
double y = ellipse.getCenterY() + Math.cos(2 * Math.PI * index / count) * ellipse.getHeight() / 2;
if (index == 0) {
coords[0] = x;
coords[1] = y;
if (affine != null)
affine.transform(coords, 0, coords, 0, 1);
return SEG_MOVETO;
}
double x0 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index - 2) / count) * ellipse.getWidth() / 2;
double y0 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index - 2) / count) * ellipse.getHeight() / 2;
double x1 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index - 1) / count) * ellipse.getWidth() / 2;
double y1 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index - 1) / count) * ellipse.getHeight() / 2;
double x2 = x;
double y2 = y;
double x3 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index + 1) / count) * ellipse.getWidth() / 2;
double y3 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index + 1) / count) * ellipse.getHeight() / 2;
double x1ctrl = x1 + (x2 - x0) / 6;
double y1ctrl = y1 + (y2 - y0) / 6;
double x2ctrl = x2 + (x1 - x3) / 6;
double y2ctrl = y2 + (y1 - y3) / 6;
coords[0] = x1ctrl;
coords[1] = y1ctrl;
coords[2] = x2ctrl;
coords[3] = y2ctrl;
coords[4] = x2;
coords[5] = y2;
if (affine != null)
affine.transform(coords, 0, coords, 0, 3);
return SEG_CUBICTO;
}
@Override
public int currentSegment(float[] coords) {
double[] temp = new double[6];
int ret = currentSegment(temp);
for (int i = 0; i < coords.length; i++)
coords[i] = (float)temp[i];
return ret;
}
};
}
}
下面是如何在代码中使用它而不是标准代码(我在这里使用 100 个片段):
g2d.draw(new BetterEllipse(100, circleCenter[0].subtract(circleRadius).doubleValue(),
circleCenter[1].subtract(circleRadius).doubleValue(), circleRadius.multiply(new BigDecimal(2)).doubleValue(),
circleRadius.multiply(new BigDecimal(2)).doubleValue()));