【问题标题】:Ellipse2D draws with poor accuracyEllipse2D 绘制精度差
【发布时间】:2015-10-11 23:26:24
【问题描述】:

我正在做一个关于空间物理学的应用程序,所以我做了很多关于轨道的事情。当然,我遇到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 没有正确绘制。但是,我尝试了很多其他角度,这就是我发现的模式:

让我相信这些计算在某种程度上是错误的。

所以这是我的问题。我应该使用 Ellipse2D 以外的东西吗?也许 Ellipse2D 不够准确?我在我的代码示例中使用了 BigDecimals,因为我认为它会给我更高的精度 - 这是错误的方法吗?我的最终目标是能够计算椭圆上某个点在特定角度的矩形位置。

提前致谢。

【问题讨论】:

    标签: java geometry ellipse


    【解决方案1】:

    您会看到此错误,因为 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()));
    

    【讨论】:

    • 美丽。非常感谢。
    猜你喜欢
    • 1970-01-01
    • 2013-05-09
    • 2013-01-27
    • 2015-08-31
    • 1970-01-01
    • 2014-12-26
    • 2012-03-14
    • 2020-07-08
    • 2021-05-05
    相关资源
    最近更新 更多