Ellipse2D绘制精度差

snickers10分钟

我正在编写有关空间物理学的应用程序,因此我在轨道上做了很多工作。自然,我遇到了Ellipse2D.Double在屏幕上绘制轨道的问题。

每当我的JPanel刷新时,我都会使用Ellipse2D绘制物体的轨道,并使用不同的方法绘制物体的轨道。

从本质上讲,我发现当数字变得很大时(无论是轨道大小变大还是可视化图像放大得很远),物体的位置和Ellipse2D都不对齐。

我使用从极坐标到直角坐标的转换来计算身体的位置,然后将Ellipse2D的数学运算放到geom程序包中。


看一下此代码示例。这是我能做的最完备的版本,因为圆的范围必须很大:

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 };
    }
}

上面的代码实际上在屏幕上很远的地方绘制了一个大半径的圆。我选择了尺寸,以便在小窗口中可以看到一个圆圈。

然后,它从圆的中心到在窗口中可见的圆上的一点画一条线:我选择了一个在窗口上可见的角度,并使用几何将该角度和圆的半径转换为直角坐标。

程序显示如下:

橱窗展示

注意,该行实际上并没有最终碰到椭圆。现在,我决定必须找出是我计算的点还是椭圆不正确。我在计算器上进行了数学运算,发现行是正确的,椭圆是不正确的:

在此处输入图片说明

考虑到计算器可能没有错,我被认为是Ellipse2D绘制不正确。但是,我尝试了许多其他角度,这是我发现的模式:

不按比例-必须更大

使我相信的计算是错误的莫名其妙。

So that's my problem. Should I be using something other than Ellipse2D? Maybe Ellipse2D is not accurate enough? I used BigDecimals in my code sample because I thought it would give me more precision - is that the wrong approach? My ultimate goal is to be able to calculate the rectangular position of a point on an ellipse at a specific angle.

Thanks in advance.

rsutormin

You see this error because Ellipse2D is approximated by four cubic curves. To make sure just take a look at its path iterator defining shape border: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/awt/geom/EllipseIterator.java#187

To improve quality we should approximate ellipse by higher number of cubic curves. Here is an extention of standard java implementation with changeable number of segments:

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()));

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章