2012-03-02 6 views
4

私はAreasとJavaで作業しています。Javaで領域を結合するときに不正確な丸め?

私のテストプログラムは3つのランダムな三角形を描画し、それらを組み合わせて1つ以上のポリゴンを形成します。 Areasの後に.add()がある場合は、PathIteratorを使用してエッジをトレースします。

ただし、Areaのオブジェクトは、必要に応じて結合されないことがあります。最後に表示された画像のように、余分なエッジが描画されます。

私は問題は(私がテストプログラムをデバッグするときにPathIteratorを使用する前に、Areaはギャップを示して)JavaのAreaクラスに不正確さを丸めによって引き起こされるが、私は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<Line2D.Double> areaSegments = new ArrayList<Line2D.Double>(); 

    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<double[]> areaPoints = new ArrayList<double[]>(); 
     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(); 
    } 
} 

成功した場合:

success

失敗した場合:

failure

答えて

4

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つの三角形 2つの三角形第3のループで 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(); 
    }  

各ループ後のパスのリセットに注意してください。

EDIT:ここでは、3つのパスを結合しようとする不正確さがあります。エラーが発生する可能性がある場所を明確にします。

First path

Second path

Third path

+0

実際の実装では、パスをリセットします。私はデモに追加するのを忘れたと思う...しかしこれは答えではありません。 – Peter

+0

@Peter:次に、使用している実際のソースを含め、ランダムに発生するたびに失敗を生成する実際の例を提供できますか? – stryba

+0

@Peter:何百ものランダム生成された組み合わせのように試してみましたが、 'reset'を追加しても失敗しませんでした。だから、あなたがランダムなジェネレータまたは実際の座標の種を見つけ出すことができれば、それはいいでしょう。そうでなければ非常に面倒です。興味深い。 – stryba

1

私はこれと一緒に遊んで、これらを取り除くためのハッキーな方法を見つけました。私は100%確信しているわけではありませんが、すべてのケースでうまくいくでしょうが、そうかもしれません。 Area.transformのjavadocが

に言及していることを読んだ後

は、指定された AffineTransformを使用して、この地域のジオメトリを変換します。 ジオメトリが変換され、 は、このオブジェクトによって定義された囲まれた領域を永久に変更します。

キーを押したままエリアを回転させる可能性が追加されました。エリアが回転するにつれて、輪郭だけが残されるまで、「内向き」エッジが徐々に消え始めました。私は、 "内側"の辺は実際には互いに非常に近い2つの辺であると考えています(単一の辺のように見える)、Areaを回転させると非常に小さな丸めの不正確さが生じるため、回転することでそれらを一緒に溶かします。

私はその後、キー入力の完全な円のために非常に小さなステップでエリアを回転させるためのコードを追加し、アーティファクトが消えるように見えます:

enter image description here

左の画像が構築されたエリアであります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); 
     } 

私が前に言ったように、私はもしわかりませんこれはすべての場合に機能し、シナリオに応じて必要なステップ量がはるかに小さくなったり大きくなったりする可能性があります。 YMMV。ここで

+0

それが問題であるならば、それを削除する簡単な方法は、正確にオーバーラップする2つの行を生成しないようにです。 random.nextInt(400)を400 * random.nextDouble()に変更することでこれを行うことができます。区間0〜400の読み取りよりも2倍以上の倍数があるので、完全一致の可能性は無視できなくなります。 –

+0

私が提案したことを試しても問題は解決しない、btw。 –

+0

これは巧妙な解決策ですが、計算上実現可能ですか?私のプログラムは、これらの計算をたくさん行うでしょう。 – Peter

2

私は両方の答えの機能を追加し、より簡単にテストするためにあなたの例をリファクタリングしました。 triangle.reset()を復元すると、私のためにアーティファクトがなくなりました。さらに、

  • event dispatch threadで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<Line2D.Double> areaSegments = new ArrayList<Line2D.Double>(); 
    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<double[]> areaPoints = new ArrayList<double[]>(); 
     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); 
      } 
     }); 
    } 
} 
+0

私はオーバーライドされた 'JPanel'を使ってアプリケーションでアクティブなレンダリングを使用します。デモでは、私はそれを簡単にしたいと思っていました。さらに、問題は計算の1つで、レンダリングではありません。「PathIterator」から取得した後で、さらに計算するためにエッジを使用します。 – Peter

関連する問題