Java-Gaming.org    
Featured games (91)
games approved by the League of Dukes
Games in Showcase (581)
games submitted by our members
Games in WIP (500)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Draw Overlapping Path with Outline  (Read 2011 times)
0 Members and 1 Guest are viewing this topic.
Offline KevinWorkman

JGO Knight


Medals: 21
Projects: 11
Exp: 12 years


klaatu barada nikto


« Posted 2013-08-24 18:45:39 »

My goal is to draw a path with both an outline and a fill color. Think about the way a figure-eight racetrack might look, or how a tangled-up wire might look. Something like this:



To accomplish this, I've tried simply using two BasicStrokes to draw a Path2D. The first BasicStroke is thicker and drawn in black, and the second is thinner and drawn in red. This almost accomplishes what I want, except the places on the curve that overlap itself are drawn as an intersection instead of an overlap:



Notice how if this was a racetrack, the cars would be able to turn anytime the track overlapped itself, when instead it should appear to be on top of itself with the black border of the top layer showing on top of the red fill of the bottom layer.

I've also tried using a custom DoubleStroke from this page, but the results are similar to the above problem.

Here's my code so far:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Defuse extends JPanel{

   Path2D.Double path = new Path2D.Double();

   public Defuse(){
     
      path.moveTo(0, 0);
     
      for(int i = 0; i < 5; i++){
         path.curveTo(Math.random()*250, Math.random()*500, Math.random()*250, Math.random()*500, Math.random()*250, Math.random()*500);
      }
   
      addMouseListener(new MouseAdapter(){
         
         
//         Point one = new Point(0, 0);
//         Point two = new Point(125, 250);
//         Point three = new Point(250, 500);
       
         @Override
         public void mouseClicked(MouseEvent e) {
           
//            if(e.getButton() == MouseEvent.BUTTON1){
//               one = e.getPoint();
//            }
//            else if(e.getButton() == MouseEvent.BUTTON2){
//               two = e.getPoint();
//            }
//            else if(e.getButton() == MouseEvent.BUTTON3){
//               three = e.getPoint();
//            }
//            
//            
           path = new Path2D.Double();
            path.moveTo(125, 0);
           
         //   path.curveTo(one.x, one.y, two.x, two.y, three.x, three.y);
           
            for(int i = 0; i < 5; i++){
               path.curveTo(Math.random()*250, Math.random()*500, Math.random()*250, Math.random()*500, Math.random()*250, Math.random()*500);
            }
            path.quadTo(path.getCurrentPoint().getX(), path.getCurrentPoint().getY(), 125, 500);
           
            repaint();
         }
      });
   }




   public void paintComponent(Graphics g){
      super.paintComponent(g);
     
      Graphics2D g2d = (Graphics2D)g;
     
      float thickness = 8;
     
      BasicStroke bs = new BasicStroke(6);
     
      g2d.setStroke(bs);
      g2d.setColor(Color.BLACK);
      g2d.draw(path);
     
      bs = new BasicStroke(thickness-4);
   
      g2d.setStroke(bs);
      g2d.setColor(Color.RED);
      g2d.draw(path);
     
     

   }


   public static void main(String... args){


      JFrame frame = new JFrame("Defuse");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.add(new Defuse());

      frame.setSize(250, 500);
      frame.setVisible(true);

   }
   
}


I'd also like to avoid sharp corners in my creation of the Path2D (cars can't make 120 degree turns around a track), but that's a separate problem for now. I appreciate any input you can give me!

Static Void Games - Play indie games, learn game programming, upload your own games!
Offline Several Kilo-Bytes

Senior Member


Medals: 11



« Reply #1 - Posted 2013-08-24 21:37:56 »

Use a path iterator. Individual line segments and cubic splines will not intersect themselves. You draw in this order black-line, black-line, black-line, red-line, red-line, red-line, so red intersections are connected and cover entire overlapped area of the red over the black. If it got drawn in this order black-line, red-line, black-line, red-line, black-line, red-line, then it will appear as if later segments overlap older ones.
Offline KevinWorkman

JGO Knight


Medals: 21
Projects: 11
Exp: 12 years


klaatu barada nikto


« Reply #2 - Posted 2013-08-24 22:35:44 »

Thanks so much for the reply. I see what you're saying about drawing it black-red black-red instead of black-black red-red. However, I tried to use a PathIterator, and I have several other problems instead. This is the code I came up with:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Defuse extends JPanel{

   Path2D.Double path = new Path2D.Double();

   public Defuse(){

      path.moveTo(0, 0);

      for(int i = 0; i < 5; i++){
         path.curveTo(Math.random()*250, Math.random()*500, Math.random()*250, Math.random()*500, Math.random()*250, Math.random()*500);
      }

      addMouseListener(new MouseAdapter(){


         @Override
         public void mouseClicked(MouseEvent e) {
 
            path = new Path2D.Double();
            path.moveTo(125, 0);
            for(int i = 0; i < 5; i++){
               path.curveTo(Math.random()*250, Math.random()*500, Math.random()*250, Math.random()*500, Math.random()*250, Math.random()*500);
            }
            path.quadTo(path.getCurrentPoint().getX(), path.getCurrentPoint().getY(), 125, 500);

            repaint();
         }
      });
   }




   public void paintComponent(Graphics g){
      super.paintComponent(g);

      Graphics2D g2d = (Graphics2D)g;

      float thickness = 8;
     
      List<Point> points = new ArrayList<Point>();

      double[] coords = new double[6];
      for(PathIterator pi = path.getPathIterator(null); !pi.isDone(); pi.next()){
         int type = pi.currentSegment(coords);
         double x = coords[0];
         double y = coords[1];

         points.add(new Point((int)x, (int)y));
      }

      for(int i = 1; i < points.size(); i++){

         Point prev = points.get(i-1);
         Point next = points.get(i);

         BasicStroke bs = new BasicStroke(thickness);
         g2d.setStroke(bs);
         g2d.setColor(Color.BLACK);
         g2d.drawLine(prev.x, prev.y, next.x, next.y);

         bs = new BasicStroke(thickness-4);
         g2d.setStroke(bs);
         g2d.setColor(Color.RED);
         g2d.drawLine(prev.x, prev.y, next.x, next.y);
      }
   }


   public static void main(String... args){

      JFrame frame = new JFrame("Defuse");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.add(new Defuse());

      frame.setSize(250, 500);
      frame.setVisible(true);

   }

}


And this is the result:



As you can see, my curves are now all straight lines, plus they're a series of rectangles with the black outline showing on bends instead of a single line like in my crude paint image above. Am I missing something obvious?

Static Void Games - Play indie games, learn game programming, upload your own games!
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Several Kilo-Bytes

Senior Member


Medals: 11



« Reply #3 - Posted 2013-08-24 23:49:44 »

A path iterator can return segments, cubic curves, or quadratic curves. You use drawLine only so things get drawn as line segments only. You could use a flattening path iterator to get just line segments or check the return type of next. The black line overlap probably could be fixed by drawing the current black line, the current red line, and the previous red line.

Offline KevinWorkman

JGO Knight


Medals: 21
Projects: 11
Exp: 12 years


klaatu barada nikto


« Reply #4 - Posted 2013-08-25 00:39:09 »

After some struggling, I came up with this:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
109  
110  
111  
112  
113  
114  
115  
116  
117  
118  
119  
120  
121  
122  
123  
124  
125  
126  
127  
128  
129  
130  
131  
132  
133  
134  
135  
136  
137  
138  
139  
140  
141  
142  
143  
144  
145  
146  
147  
148  
149  
150  
151  
152  
153  
154  
155  
156  
157  
158  
159  
160  
161  
162  
163  
164  
165  
166  
167  
168  
169  
170  
171  
172  
173  
174  
175  
176  
177  
178  
179  
180  
181  
182  
183  
184  
185  
186  
187  
188  
189  
190  
191  
192  
193  
194  
195  
196  
197  
198  
199  
200  
201  
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Defuse extends JPanel{

   Path2D.Double path = new Path2D.Double();
   Point2D.Double prevPoint = null;

   public Defuse(){

      path.moveTo(0, 0);

      for(int i = 0; i < 3; i++){
         path.curveTo(Math.random()*250, Math.random()*500, Math.random()*250, Math.random()*500, Math.random()*250, Math.random()*500);
      }

      addMouseListener(new MouseAdapter(){


         @Override
         public void mouseClicked(MouseEvent e) {

            path = new Path2D.Double();
            path.moveTo(125, 0);
            for(int i = 0; i < 2; i++){
               path.curveTo(Math.random()*250, Math.random()*500, Math.random()*250, Math.random()*500, Math.random()*250, Math.random()*500);
            }
            path.curveTo(path.getCurrentPoint().getX(), path.getCurrentPoint().getY(), Math.random()*250, Math.random()*500, 125, 500);

            repaint();
         }
      });
   }




   public void paintComponent(Graphics g){
      super.paintComponent(g);

      Graphics2D g2d = (Graphics2D)g;

      final float thickness = 8;


      double[] coords = new double[6];


      Painter previousPainter = null;

      for(PathIterator pi = path.getPathIterator(null); !pi.isDone(); pi.next()){
         int type = pi.currentSegment(coords);





         if(type == PathIterator.SEG_MOVETO){
            //one point
           System.out.println("Type: SEG_MOVETO");
            prevPoint = new Point2D.Double(coords[0], coords[1]);
         }
         else if(type == PathIterator.SEG_LINETO){
            //one point
           System.out.println("Type: SEG_LINETO");

            final Point2D.Double nextPoint = new Point2D.Double(coords[0], coords[1]);


            BasicStroke bs = new BasicStroke(thickness);
            g2d.setStroke(bs);
            g2d.setColor(Color.BLACK);
            g2d.drawLine((int)prevPoint.x, (int)prevPoint.y, (int)nextPoint.x, (int)nextPoint.y);

            if(previousPainter != null){
               previousPainter.paint(g2d);
            }

            previousPainter = new Painter(){
               public void paint(Graphics2D g2d){
                  BasicStroke bs = new BasicStroke(thickness-4);
                  g2d.setStroke(bs);
                  g2d.setColor(Color.RED);
                  g2d.drawLine((int)prevPoint.x, (int)prevPoint.y, (int)nextPoint.x, (int)nextPoint.y);
               }
            };

            previousPainter.paint(g2d);


            prevPoint = nextPoint;
         }
         else if(type == PathIterator.SEG_QUADTO){
            //two points

            System.out.println("Type: SEG_QUADTO");

            Point2D.Double currentPoint = new Point2D.Double(coords[0], coords[1]);
            Point2D.Double nextPoint = new Point2D.Double(coords[2], coords[3]);


            final Path2D.Double path = new Path2D.Double();
            path.moveTo(prevPoint.x, prevPoint.y);
            path.quadTo(currentPoint.x, currentPoint.y, nextPoint.x, nextPoint.y);

            BasicStroke bs = new BasicStroke(thickness);
            g2d.setStroke(bs);
            g2d.setColor(Color.BLACK);
            g2d.draw(path);


            if(previousPainter != null){
               previousPainter.paint(g2d);
            }

            previousPainter = new Painter(){
               public void paint(Graphics2D g2d){
                  BasicStroke bs = new BasicStroke(thickness-4);
                  g2d.setStroke(bs);
                  g2d.setColor(Color.RED);
                  g2d.draw(path);
               }
            };

            previousPainter.paint(g2d);

            prevPoint = nextPoint;


         }
         else if(type == PathIterator.SEG_CUBICTO){
            //three points

            System.out.println("Type: SEG_CUBICTO");

            Point2D.Double middlePoint1 = new Point2D.Double(coords[0], coords[1]);
            Point2D.Double middlePoint2 = new Point2D.Double(coords[2], coords[3]);
            Point2D.Double nextPoint = new Point2D.Double(coords[4], coords[5]);


            final Path2D.Double path = new Path2D.Double();
            path.moveTo(prevPoint.x, prevPoint.y);
            path.curveTo(middlePoint1.x, middlePoint1.y, middlePoint2.x, middlePoint2.y, nextPoint.x, nextPoint.y);

            BasicStroke bs = new BasicStroke(thickness);
            g2d.setStroke(bs);
            g2d.setColor(Color.BLACK);
            g2d.draw(path);

            if(previousPainter != null){
               previousPainter.paint(g2d);
            }

            previousPainter = new Painter(){
               public void paint(Graphics2D g2d){
                  BasicStroke bs = new BasicStroke(thickness-4);
                  g2d.setStroke(bs);
                  g2d.setColor(Color.RED);
                  g2d.draw(path);
               }
            };
           
            previousPainter.paint(g2d);

            prevPoint = nextPoint;
         }
         else if(type == PathIterator.SEG_CLOSE){
            //no points
           System.out.println("Type: SEG_CLOSE");
         }
      }

   }

   private static interface Painter{
      public void paint(Graphics2D g2d);
   }


   public static void main(String... args){

      JFrame frame = new JFrame("Defuse");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.add(new Defuse());

      frame.setSize(250, 500);
      frame.setVisible(true);

   }

}


Quote
The black line overlap probably could be fixed by drawing the current black line, the current red line, and the previous red line.

And that almost works, except when the previous red line overlaps another curve that should be drawn on top of it. Here's an example: (the curve is drawn from the top down)



The first curve comes from the top to the lower right, then the second one curves back towards the middle and over the first line. It then draws the previous curve, which draws the first curve over top of the second curves outline. A similar thing happens with the third curve and the second curve. You can see that the third curve does indeed go on top of the first one, but the other two cases are still a problem. Ack!

Static Void Games - Play indie games, learn game programming, upload your own games!
Offline Abuse

JGO Coder


Medals: 10


falling into the abyss of reality


« Reply #5 - Posted 2013-08-25 02:43:10 »

The way the Java2D rasterizer works makes it impossible to do what you're trying to do.

I'd suggest preprocess the Path to split it when it intersects itself.
The other alternative is to rasterize the path yourself.

Alternatively you could add an extra dimension to your path, and render it in 3d (using the zbuffer to handle the overdraw).

Make Elite IV:Dangerous happen! Pledge your backing at KICKSTARTER here! https://dl.dropbox.com/u/54785909/EliteIVsmaller.png
Offline KevinWorkman

JGO Knight


Medals: 21
Projects: 11
Exp: 12 years


klaatu barada nikto


« Reply #6 - Posted 2013-08-25 04:21:22 »

The way the Java2D rasterizer works makes it impossible to do what you're trying to do.

Gah, that's what I was beginning to be afraid of. Thanks for the reply though.

I'd suggest preprocess the Path to split it when it intersects itself.
The other alternative is to rasterize the path yourself.

Alternatively you could add an extra dimension to your path, and render it in 3d (using the zbuffer to handle the overdraw).

Using the z-axis makes a lot of sense, but I'm having trouble googling how to get started with that method. (Actually google led me to an explantion on z-order that I originally wrote to somebody else on the old sun forums, weird.) I don't suppose you have a direction to point me in?

I don't have any experience using paths, and this is for Ludum Dare, so the solution might be to just not worry about it. But I'd love to get this working, as I think it'll really add to what I'm doing. Either way, thanks to both of you for the replies so far!

Static Void Games - Play indie games, learn game programming, upload your own games!
Offline Several Kilo-Bytes

Senior Member


Medals: 11



« Reply #7 - Posted 2013-08-25 18:49:28 »

Sorry, I did not see that you were randomly generating your curve. If you don't have control over the points, then you need to check if curves intersect themselves and other lines. I still assume this is just a graphical effect and not actually a race track because I would go in a completely different direction.

For generating the curve: To avoid bends you want to make sure that the last control point of the previous curve, the first control point of the current curve, and the end point shared between them are collinear. This is because Bezier curves are tangent to the line between the end point and the nearest control point at the start and end of the curve. A curve might still be too sharp, but there won't be angled bends. A way to estimate how sharp a turn is would be to use three point Bezier curves would be to measure the angle between the three points. (I am pretty sure a four point with two identical control points is equivalent.)

You can get proper z ordering either by rendering pieces from back to front or using a z-buffer. I doubt I would use a z-buffer in my Java2D programs, but I think a custom composite class would be the way to do it.
Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

xsi3rr4x (63 views)
2014-04-15 18:08:23

BurntPizza (61 views)
2014-04-15 03:46:01

UprightPath (74 views)
2014-04-14 17:39:50

UprightPath (57 views)
2014-04-14 17:35:47

Porlus (75 views)
2014-04-14 15:48:38

tom_mai78101 (100 views)
2014-04-10 04:04:31

BurntPizza (160 views)
2014-04-08 23:06:04

tom_mai78101 (255 views)
2014-04-05 13:34:39

trollwarrior1 (208 views)
2014-04-04 12:06:45

CJLetsGame (215 views)
2014-04-01 02:16:10
List of Learning Resources
by SHC
2014-04-18 03:17:39

List of Learning Resources
by Longarmx
2014-04-08 03:14:44

Good Examples
by matheus23
2014-04-05 13:51:37

Good Examples
by Grunnt
2014-04-03 15:48:46

Good Examples
by Grunnt
2014-04-03 15:48:37

Good Examples
by matheus23
2014-04-01 18:40:51

Good Examples
by matheus23
2014-04-01 18:40:34

Anonymous/Local/Inner class gotchas
by Roquen
2014-03-11 15:22:30
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!