View Javadoc
1   package org.opentrafficsim.draw.road;
2   
3   import java.awt.BasicStroke;
4   import java.awt.Color;
5   import java.awt.Graphics2D;
6   import java.awt.geom.Path2D;
7   import java.awt.image.ImageObserver;
8   import java.rmi.RemoteException;
9   import java.util.ArrayList;
10  import java.util.List;
11  import java.util.Set;
12  
13  import javax.naming.NamingException;
14  
15  import org.djunits.value.vdouble.scalar.Length;
16  import org.djutils.draw.line.PolyLine2d;
17  import org.djutils.draw.line.Polygon2d;
18  import org.djutils.draw.point.OrientedPoint2d;
19  import org.djutils.draw.point.Point2d;
20  import org.opentrafficsim.base.geometry.OtsLocatable;
21  import org.opentrafficsim.base.geometry.OtsRenderable;
22  import org.opentrafficsim.draw.DrawLevel;
23  import org.opentrafficsim.draw.PaintPolygons;
24  import org.opentrafficsim.draw.road.StripeAnimation.StripeData;
25  
26  import nl.tudelft.simulation.naming.context.Contextualized;
27  
28  /**
29   * Draw road stripes.
30   * <p>
31   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
32   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
33   * </p>
34   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
35   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
36   */
37  public class StripeAnimation extends OtsRenderable<StripeData>
38  {
39      /** */
40      private static final long serialVersionUID = 20141017L;
41  
42      /** Drawable paths. */
43      private final Set<Path2D.Float> paths;
44  
45      /**
46       * @param source StripeData; stripe data
47       * @param contextualized Contextualized; context provider
48       * @throws NamingException ne
49       * @throws RemoteException on communication failure
50       */
51      public StripeAnimation(final StripeData source, final Contextualized contextualized) throws NamingException, RemoteException
52      {
53          super(source, contextualized);
54          List<Point2d> list = makePoints(source);
55          if (!list.isEmpty())
56          {
57              this.paths = PaintPolygons.getPaths(getSource().getLocation(), list);
58          }
59          else
60          {
61              // no dash within length
62              this.paths = null;
63          }
64      }
65  
66      /**
67       * Generate the drawing commands for a dash pattern.
68       * @param center PolyLine2d; the design line of the striped pattern
69       * @param width double; width of the stripes in meters
70       * @param startOffset double; shift the starting point in the pattern by this length in meters
71       * @param onOffLengths double[]; one or more lengths of the dashes and the gaps between those dashes. If the number of
72       *            values in <cite>onOffLengths</cite> is odd, the pattern repeats inverted. The first value in
73       *            <cite>onOffLengths</cite> is the length of a dash.
74       * @return ArrayList&lt;Coordinate&gt;; the coordinates of the dashes separated and terminated by a <cite>NEWPATH</cite>
75       *         Coordinate
76       */
77      private ArrayList<Point2d> makeDashes(final PolyLine2d center, final double width, final double startOffset,
78              final double[] onOffLengths)
79      {
80          double period = 0;
81          for (double length : onOffLengths)
82          {
83              if (length < 0)
84              {
85                  throw new Error("Bad pattern - on or off length is < 0");
86              }
87              period += length;
88          }
89          if (period <= 0)
90          {
91              throw new Error("Bad pattern - repeat period length is 0");
92          }
93          double length = center.getLength();
94          double position = -startOffset;
95          int phase = 0;
96          ArrayList<Point2d> result = new ArrayList<>();
97          while (position < length)
98          {
99              double nextBoundary = position + onOffLengths[phase++ % onOffLengths.length];
100             if (nextBoundary > 0) // Skip this one; this entire dash lies within the startOffset
101             {
102                 if (position < 0)
103                 {
104                     position = 0; // Draw a partial dash, starting at 0 (begin of the center line)
105                 }
106                 double endPosition = nextBoundary;
107                 if (endPosition > length)
108                 {
109                     endPosition = length; // Draw a partial dash, ending at length (end of the center line)
110                 }
111 
112                 PolyLine2d dashCenter;
113                 dashCenter = center.extract(position, endPosition);
114                 dashCenter.offsetLine(width / 2).getPoints().forEachRemaining(result::add);
115                 dashCenter.offsetLine(-width / 2).reverse().getPoints().forEachRemaining(result::add);
116                 result.add(PaintPolygons.NEWPATH);
117             }
118             position = nextBoundary + onOffLengths[phase++ % onOffLengths.length];
119         }
120         return result;
121     }
122 
123     /**
124      * Generate the points needed to draw the stripe pattern.
125      * @param stripe StripeData; the stripe
126      * @return Coordinate[]; array of Coordinate
127      * @throws NamingException when <cite>type</cite> is not supported
128      */
129     private List<Point2d> makePoints(final StripeData stripe) throws NamingException
130     {
131         double width = stripe.getWidth().si;
132         switch (stripe.getType())
133         {
134             case DASHED:// ¦ - Draw a 3-9 dash pattern on the center line
135                 return makeDashes(stripe.getCenterLine(), width, 3.0, new double[] {3, 9});
136 
137             case BLOCK:// : - Draw a 1-3 dash pattern on the center line
138                 return makeDashes(stripe.getCenterLine(), width, 1.0, new double[] {1, 3});
139 
140             case DOUBLE:// ||- Draw two solid lines
141             {
142                 PolyLine2d centerLine = stripe.getCenterLine();
143                 List<Point2d> result = new ArrayList<>(centerLine.size() * 4 + 1);
144                 centerLine.offsetLine(width / 2).getPoints().forEachRemaining(result::add);
145                 centerLine.offsetLine(width / 6).reverse().getPoints().forEachRemaining(result::add);
146                 result.add(PaintPolygons.NEWPATH);
147                 centerLine.offsetLine(-width / 2).getPoints().forEachRemaining(result::add);
148                 centerLine.offsetLine(-width / 6).reverse().getPoints().forEachRemaining(result::add);
149                 return result;
150             }
151 
152             case LEFT: // |¦ - Draw left solid, right 3-9 dashed
153             {
154                 PolyLine2d centerLine = stripe.getCenterLine();
155                 List<Point2d> result = makeDashes(centerLine.offsetLine(-width / 3), width / 3, 0.0, new double[] {3, 9});
156                 result.add(PaintPolygons.NEWPATH);
157                 centerLine.offsetLine(width / 2).getPoints().forEachRemaining(result::add);
158                 centerLine.offsetLine(width / 6).reverse().getPoints().forEachRemaining(result::add);
159                 return result;
160             }
161 
162             case RIGHT: // ¦| - Draw left 3-9 dashed, right solid
163             {
164                 PolyLine2d centerLine = stripe.getCenterLine();
165                 ArrayList<Point2d> result = makeDashes(centerLine.offsetLine(width / 3), width / 3, 0.0, new double[] {3, 9});
166                 result.add(PaintPolygons.NEWPATH);
167                 centerLine.offsetLine(-width / 2).getPoints().forEachRemaining(result::add);
168                 centerLine.offsetLine(-width / 6).reverse().getPoints().forEachRemaining(result::add);
169                 return result;
170             }
171 
172             case SOLID: // | - Draw single solid line
173                 PolyLine2d centerLine = stripe.getCenterLine();
174                 PolyLine2d leftEdge = centerLine.offsetLine(stripe.getWidth().si / 2.0);
175                 PolyLine2d rightEdge = centerLine.offsetLine(-stripe.getWidth().si / 2.0);
176                 List<Point2d> list = leftEdge.getPointList();
177                 list.addAll(rightEdge.reverse().getPointList());
178                 List<Point2d> result = new Polygon2d(list).getPointList();
179                 return result;
180 
181             default:
182                 throw new NamingException("Unsupported stripe type: " + stripe.getType());
183         }
184 
185     }
186 
187     /** {@inheritDoc} */
188     @Override
189     public final void paint(final Graphics2D graphics, final ImageObserver observer)
190     {
191         if (this.paths != null)
192         {
193             setRendering(graphics);
194             graphics.setStroke(new BasicStroke(2.0f));
195             PaintPolygons.paintPaths(graphics, Color.WHITE, this.paths, true);
196             resetRendering(graphics);
197         }
198     }
199 
200     /** {@inheritDoc} */
201     @Override
202     public final String toString()
203     {
204         return "StripeAnimation [source = " + getSource().toString() + ", paths=" + this.paths + "]";
205     }
206 
207     /**
208      * StripeData provides the information required to draw a stripe.
209      * <p>
210      * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
211      * <br>
212      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
213      * </p>
214      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
215      */
216     public interface StripeData extends OtsLocatable
217     {
218         /** {@inheritDoc} */
219         @Override
220         OrientedPoint2d getLocation();
221 
222         /**
223          * Returns the center line.
224          * @return PolyLine2d; center line.
225          */
226         PolyLine2d getCenterLine();
227 
228         /**
229          * Returns the stripe type.
230          * @return Type; stripe type.
231          */
232         Type getType();
233 
234         /**
235          * Returns the line width.
236          * @return Length; line width.
237          */
238         Length getWidth();
239 
240         /** {@inheritDoc} */
241         @Override
242         default double getZ()
243         {
244             return DrawLevel.MARKING.getZ();
245         }
246 
247         /**
248          * Stripe type (same fields as org.opentrafficsim.road.network.lane.Stripe.Type).
249          * <p>
250          * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights
251          * reserved. <br>
252          * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
253          * </p>
254          * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
255          */
256         public enum Type
257         {
258             /** Single solid line. */
259             SOLID,
260 
261             /** Line |¦ allow to go to left, but not to right. */
262             LEFT,
263 
264             /** Line ¦| allow to go to right, but not to left. */
265             RIGHT,
266 
267             /** Dashes ¦ allow to cross in both directions. */
268             DASHED,
269 
270             /** Double solid line ||, don't cross. */
271             DOUBLE,
272 
273             /** Block : allow to cross in both directions. */
274             BLOCK;
275         }
276     }
277 }