在Java中组合区域时舍入不准确?

我正在使用Java中的Areas

我的测试程序绘制三个随机三角形并将它们组合成一个或多个多边形。 Areas.add()编辑后,我使用PathIterator跟踪边缘。

但是,有时候,“ Area对象不会按照它们的组合进行组合……正如您在上一张图片中看到的那样,将绘制额外的边缘。

认为问题是由Java的Area类中的舍入不准确引起的(当我调试测试程序时, Area在使用PathIterator之前显示间隙),但我不认为Java提供了任何其他方式来组合形状。

有解决方案?

示例代码和图像:

 import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.geom.Area; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; import java.util.ArrayList; import java.util.Random; import javax.swing.JFrame; public class AreaTest extends JFrame{ private static final long serialVersionUID = -2221432546854106311L; Area area = new Area(); ArrayList areaSegments = new ArrayList(); AreaTest() { Path2D.Double triangle = new Path2D.Double(); Random random = new Random(); // Draw three random triangles for (int i = 0; i < 3; i++) { triangle.moveTo(random.nextInt(400) + 50, random.nextInt(400) + 50); triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50); triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50); triangle.closePath(); area.add(new Area(triangle)); } // Note: we're storing double[] and not Point2D.Double ArrayList areaPoints = new ArrayList(); double[] coords = new double[6]; for (PathIterator pi = area.getPathIterator(null); !pi.isDone(); pi.next()) { // Because the Area is composed of straight lines int type = pi.currentSegment(coords); // We record a double array of {segment type, x coord, y coord} double[] pathIteratorCoords = {type, coords[0], coords[1]}; areaPoints.add(pathIteratorCoords); } double[] start = new double[3]; // To record where each polygon starts for (int i = 0; i < areaPoints.size(); i++) { // If we're not on the last point, return a line from this point to the next double[] currentElement = areaPoints.get(i); // We need a default value in case we've reached the end of the ArrayList double[] nextElement = {-1, -1, -1}; if (i < areaPoints.size() - 1) { nextElement = areaPoints.get(i + 1); } // Make the lines if (currentElement[0] == PathIterator.SEG_MOVETO) { start = currentElement; // Record where the polygon started to close it later } if (nextElement[0] == PathIterator.SEG_LINETO) { areaSegments.add( new Line2D.Double( currentElement[1], currentElement[2], nextElement[1], nextElement[2] ) ); } else if (nextElement[0] == PathIterator.SEG_CLOSE) { areaSegments.add( new Line2D.Double( currentElement[1], currentElement[2], start[1], start[2] ) ); } } setSize(new Dimension(500, 500)); setLocationRelativeTo(null); // To center the JFrame on screen setDefaultCloseOperation(EXIT_ON_CLOSE); setResizable(false); setVisible(true); } public void paint(Graphics g) { // Fill the area Graphics2D g2d = (Graphics2D) g; g.setColor(Color.lightGray); g2d.fill(area); // Draw the border line by line g.setColor(Color.black); for (Line2D.Double line : areaSegments) { g2d.draw(line); } } public static void main(String[] args) { new AreaTest(); } } 

一个成功案例:

成功

一个失败的案例:

失败

这里:

  for (int i = 0; i < 3; i++) { triangle.moveTo(random.nextInt(400) + 50, random.nextInt(400) + 50); triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50); triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50); triangle.closePath(); area.add(new Area(triangle)); } 

实际上,您在第一个循环中添加了1个三角形,在第三个循环中添加了第二个循环中的3个三角形

这是您的不准确之处。 试试这个,看看你的问题是否仍然存在。

  for (int i = 0; i < 3; i++) { triangle.moveTo(random.nextInt(400) + 50, random.nextInt(400) + 50); triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50); triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50); triangle.closePath(); area.add(new Area(triangle)); triangle.reset(); } 

请注意每次循环后路径重置。

编辑:解释更多来自这里的不准确的地方你试图结合的三条路径。 这显然可能出现错误。

第一条路

第二条道路

第三条道路

我重新考虑了你的例子,使测试更容易,添加了两个答案的function。 恢复triangle.reset()似乎消除了我的艺术品。 此外,

  • 在事件派发线程上构建GUI。

  • 对于渲染,扩展JComponent ,例如JPanel ,并覆盖paintComponent()

  • 如果缺少具有首选大小的子组件,则覆盖getPreferredSize()

  • 使用RenderingHints

SSCCE :

 import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; import java.util.ArrayList; import java.util.List; import java.util.Random; import javax.swing.AbstractAction; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JSpinner; import javax.swing.SpinnerNumberModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** @see http://stackoverflow.com/q/9526835/230513 */ public class AreaTest extends JPanel { private static final int SIZE = 500; private static final int INSET = SIZE / 10; private static final int BOUND = SIZE - 2 * INSET; private static final int N = 5; private static final AffineTransform I = new AffineTransform(); private static final double FLATNESS = 1; private static final Random random = new Random(); private Area area = new Area(); private List areaSegments = new ArrayList(); private int count = N; AreaTest() { setLayout(new BorderLayout()); create(); add(new JPanel() { @Override public void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setColor(Color.lightGray); g2d.fill(area); g.setColor(Color.black); for (Line2D.Double line : areaSegments) { g2d.draw(line); } } @Override public Dimension getPreferredSize() { return new Dimension(SIZE, SIZE); } }); JPanel control = new JPanel(); control.add(new JButton(new AbstractAction("Update") { @Override public void actionPerformed(ActionEvent e) { create(); repaint(); } })); JSpinner countSpinner = new JSpinner(); countSpinner.setModel(new SpinnerNumberModel(N, 3, 42, 1)); countSpinner.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { JSpinner s = (JSpinner) e.getSource(); count = ((Integer) s.getValue()).intValue(); } }); control.add(countSpinner); add(control, BorderLayout.SOUTH); } private int randomPoint() { return random.nextInt(BOUND) + INSET; } private void create() { area.reset(); areaSegments.clear(); Path2D.Double triangle = new Path2D.Double(); // Draw three random triangles for (int i = 0; i < count; i++) { triangle.moveTo(randomPoint(), randomPoint()); triangle.lineTo(randomPoint(), randomPoint()); triangle.lineTo(randomPoint(), randomPoint()); triangle.closePath(); area.add(new Area(triangle)); triangle.reset(); } // Note: we're storing double[] and not Point2D.Double List areaPoints = new ArrayList(); double[] coords = new double[6]; for (PathIterator pi = area.getPathIterator(I, FLATNESS); !pi.isDone(); pi.next()) { // Because the Area is composed of straight lines int type = pi.currentSegment(coords); // We record a double array of {segment type, x coord, y coord} double[] pathIteratorCoords = {type, coords[0], coords[1]}; areaPoints.add(pathIteratorCoords); } // To record where each polygon starts double[] start = new double[3]; for (int i = 0; i < areaPoints.size(); i++) { // If we're not on the last point, return a line from this point to the next double[] currentElement = areaPoints.get(i); // We need a default value in case we've reached the end of the List double[] nextElement = {-1, -1, -1}; if (i < areaPoints.size() - 1) { nextElement = areaPoints.get(i + 1); } // Make the lines if (currentElement[0] == PathIterator.SEG_MOVETO) { // Record where the polygon started to close it later start = currentElement; } if (nextElement[0] == PathIterator.SEG_LINETO) { areaSegments.add( new Line2D.Double( currentElement[1], currentElement[2], nextElement[1], nextElement[2])); } else if (nextElement[0] == PathIterator.SEG_CLOSE) { areaSegments.add( new Line2D.Double( currentElement[1], currentElement[2], start[1], start[2])); } } } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { JFrame f = new JFrame(); f.add(new AreaTest()); f.pack(); f.setLocationRelativeTo(null); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setResizable(false); f.setVisible(true); } }); } } 

我玩弄了这个,找到了摆脱这些的hacky方法。 我不是100%确定这会在所有情况下都有效,但它可能会。

看完Area.transform的JavaDoc后提到

使用指定的AffineTransform变换此Area的几何。 几何体将在适当位置进行转换,从而永久性地更改此对象定义的封闭区域。

我有一种预感,并且通过按住一个键来增加旋转区域的可能性。 当区域旋转时,“向内”边缘开始慢慢消失,直到只剩下轮廓。 我怀疑“向内”边缘实际上是两条边缘彼此非常接近(因此它们看起来像一条边缘),并且旋转区域会导致非常小的圆角不准确,因此旋转类型将它们“融合”在一起。

然后我添加了一个代码,用于在非常小的步骤中旋转区域,在按键上旋转一整圈,看起来工件消失了:

在此处输入图像描述

左边的图像是由10个不同的随机三角形构成的区域(我增加三角形的数量以更频繁地获得“失败”区域),右边的图像是相同的区域,在旋转360度以后非常小增量(10000步)。

这是用于以小步长旋转区域的代码段(对于大多数情况,小于10000步的量可能会很好):

  final int STEPS = 10000; //Number of steps in a full 360 degree rotation double theta = (2*Math.PI) / STEPS; //Single step "size" in radians Rectangle bounds = area.getBounds(); //Getting the bounds to find the center of the Area AffineTransform trans = AffineTransform.getRotateInstance(theta, bounds.getCenterX(), bounds.getCenterY()); //Transformation matrix for theta radians around the center //Rotate a full 360 degrees in small steps for(int i = 0; i < STEPS; i++) { area.transform(trans); } 

正如我之前所说,我不确定这是否适用于所有情况,并且所需的步骤数量可能会更小或更大,具体取决于方案。 因人而异。