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.image.ImageObserver;
7   import java.io.Serializable;
8   import java.rmi.RemoteException;
9   import java.util.ArrayList;
10  import java.util.Arrays;
11  
12  import javax.naming.NamingException;
13  
14  import org.locationtech.jts.geom.Coordinate;
15  import org.locationtech.jts.geom.Geometry;
16  import org.locationtech.jts.linearref.LengthIndexedLine;
17  import org.locationtech.jts.operation.buffer.BufferParameters;
18  import org.opentrafficsim.core.geometry.OTSGeometryException;
19  import org.opentrafficsim.core.geometry.OTSLine3D;
20  import org.opentrafficsim.core.geometry.OTSPoint3D;
21  import org.opentrafficsim.draw.core.ClonableRenderable2DInterface;
22  import org.opentrafficsim.draw.core.PaintPolygons;
23  import org.opentrafficsim.road.network.lane.Stripe;
24  
25  import nl.tudelft.simulation.dsol.animation.D2.Renderable2D;
26  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  public class StripeAnimation extends Renderable2D<Stripe> implements ClonableRenderable2DInterface<Stripe>, Serializable
39  {
40      
41      private static final long serialVersionUID = 20141017L;
42  
43      
44      private final TYPE type;
45  
46      
47      private final OTSLine3D line;
48  
49      
50      private static final int QUADRANTSEGMENTS = 8;
51  
52      
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63      
64      private ArrayList<OTSPoint3D> makeDashes(final LengthIndexedLine center, final double width, final double startOffset,
65              final double[] onOffLengths)
66      {
67          double period = 0;
68          for (double length : onOffLengths)
69          {
70              if (length < 0)
71              {
72                  throw new Error("Bad pattern - on or off length is < 0");
73              }
74              period += length;
75          }
76          if (period <= 0)
77          {
78              throw new Error("Bad pattern - repeat period length is 0");
79          }
80          double length = center.getEndIndex();
81          double position = -startOffset;
82          int phase = 0;
83          ArrayList<OTSPoint3D> result = new ArrayList<>();
84          while (position < length)
85          {
86              double nextBoundary = position + onOffLengths[phase++ % onOffLengths.length];
87              if (nextBoundary > 0) 
88              {
89                  if (position < 0)
90                  {
91                      position = 0; 
92                  }
93                  double endPosition = nextBoundary;
94                  if (endPosition > length)
95                  {
96                      endPosition = length; 
97                  }
98                  Coordinate[] oneDash = center.extractLine(position, endPosition)
99                          .buffer(width / 2, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
100                 for (int i = 0; i < oneDash.length; i++)
101                 {
102                     result.add(new OTSPoint3D(oneDash[i]));
103                 }
104                 result.add(PaintPolygons.NEWPATH);
105             }
106             position = nextBoundary + onOffLengths[phase++ % onOffLengths.length];
107         }
108         return result;
109     }
110 
111     
112 
113 
114 
115 
116 
117 
118     private ArrayList<OTSPoint3D> makePoints(final Stripe stripe, final TYPE stripeType) throws NamingException
119     {
120         switch (this.type)
121         {
122             case DASHED:
123                 return makeDashes(new LengthIndexedLine(stripe.getCenterLine().getLineString()), 0.2, 3.0, new double[] {3, 9});
124 
125             case BLOCK:
126                 return makeDashes(new LengthIndexedLine(stripe.getCenterLine().getLineString()), 0.45, 1.0,
127                         new double[] {1, 3});
128 
129             case DOUBLE:
130             {
131                 OTSLine3D centerLine = stripe.getCenterLine();
132                 Coordinate[] leftLine = centerLine.offsetLine(0.2).getLineString()
133                         .buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
134                 Coordinate[] rightLine = centerLine.offsetLine(-0.2).getLineString()
135                         .buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
136                 ArrayList<OTSPoint3D> result = new ArrayList<>(leftLine.length + rightLine.length);
137                 for (int i = 0; i < leftLine.length; i++)
138                 {
139                     result.add(new OTSPoint3D(leftLine[i]));
140                 }
141                 for (int i = 0; i < rightLine.length; i++)
142                 {
143                     result.add(new OTSPoint3D(rightLine[i]));
144                 }
145                 return result;
146             }
147 
148             case LEFTONLY: 
149             {
150                 OTSLine3D centerLine = stripe.getCenterLine();
151                 Geometry rightDesignLine = centerLine.offsetLine(-0.2).getLineString();
152                 ArrayList<OTSPoint3D> result =
153                         makeDashes(new LengthIndexedLine(rightDesignLine), 0.2, 0.0, new double[] {3, 9});
154                 Coordinate[] leftCoordinates = centerLine.offsetLine(0.2).getLineString()
155                         .buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
156                 for (int i = 0; i < leftCoordinates.length; i++)
157                 {
158                     result.add(new OTSPoint3D(leftCoordinates[i]));
159                 }
160                 result.add(PaintPolygons.NEWPATH);
161                 return result;
162             }
163 
164             case RIGHTONLY: 
165             {
166                 OTSLine3D centerLine = stripe.getCenterLine();
167                 Geometry leftDesignLine = centerLine.offsetLine(0.2).getLineString();
168                 ArrayList<OTSPoint3D> result = makeDashes(new LengthIndexedLine(leftDesignLine), 0.2, 0.0, new double[] {3, 9});
169                 Coordinate[] rightCoordinates = centerLine.offsetLine(-0.2).getLineString()
170                         .buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
171                 for (int i = 0; i < rightCoordinates.length; i++)
172                 {
173                     result.add(new OTSPoint3D(rightCoordinates[i]));
174                 }
175                 result.add(PaintPolygons.NEWPATH);
176                 return result;
177             }
178 
179             case SOLID:
180                 return new ArrayList<>(Arrays.asList(stripe.getContour().getPoints()));
181 
182             default:
183                 throw new NamingException("Unsupported stripe type: " + stripeType);
184         }
185 
186     }
187 
188     
189 
190 
191 
192 
193 
194 
195 
196     public StripeAnimation(final Stripe source, final SimulatorInterface.TimeDoubleUnit simulator, final TYPE type)
197             throws NamingException, RemoteException, OTSGeometryException
198     {
199         super(source, simulator);
200         this.type = type;
201         ArrayList<OTSPoint3D> list = makePoints(source, type);
202         if (!list.isEmpty())
203         {
204             this.line = new OTSLine3D(list);
205         }
206         else
207         {
208             
209             this.line = null;
210         }
211     }
212 
213     
214     @Override
215     public final void paint(final Graphics2D graphics, final ImageObserver observer)
216     {
217         if (this.line != null)
218         {
219             graphics.setStroke(new BasicStroke(2.0f));
220             PaintPolygons.paintMultiPolygon(graphics, Color.WHITE, getSource().getLocation(), this.line, true);
221         }
222     }
223 
224     
225 
226 
227 
228 
229 
230 
231     public enum TYPE
232     {
233         
234         SOLID,
235 
236         
237         LEFTONLY,
238 
239         
240         RIGHTONLY,
241 
242         
243         DASHED,
244 
245         
246         DOUBLE,
247 
248         
249         BLOCK
250     }
251 
252     
253     @Override
254     @SuppressWarnings("checkstyle:designforextension")
255     public ClonableRenderable2DInterface<Stripe> clone(final Stripe newSource,
256             final SimulatorInterface.TimeDoubleUnit newSimulator) throws NamingException, RemoteException
257     {
258         try
259         {
260             return new StripeAnimation(newSource, newSimulator, this.type);
261         }
262         catch (OTSGeometryException exception)
263         {
264             throw new RemoteException("Stripe animation clone failed", exception);
265         }
266     }
267 
268     
269     @Override
270     public final String toString()
271     {
272         return "StripeAnimation [source = " + getSource().toString() + ", type=" + this.type + ", line=" + this.line + "]";
273     }
274 }