Ellipse2D рисует с плохой точностью

Я делаю приложение о космической физике, так что я много работаю с орбитами. Естественно, я сталкиваюсь с 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 в своем примере кода, потому что думал, что это даст мне больше точности — это неправильный подход? Моя конечная цель — вычислить прямоугольное положение точки на эллипсе под определенным углом.

Заранее спасибо.


person snickers10m    schedule 11.10.2015    source источник


Ответы (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()));
person rsutormin    schedule 12.10.2015
comment
Красивый. Огромное спасибо. - person snickers10m; 13.10.2015