View Javadoc
1   package org.opentrafficsim.base.geometry;
2   
3   import java.awt.Graphics2D;
4   import java.awt.RenderingHints;
5   import java.awt.RenderingHints.Key;
6   import java.util.Map;
7   import java.util.WeakHashMap;
8   
9   import org.djutils.draw.Oriented;
10  import org.djutils.draw.Transform2d;
11  import org.djutils.draw.bounds.Bounds2d;
12  import org.djutils.draw.point.Point2d;
13  import org.djutils.exceptions.Throw;
14  
15  import nl.tudelft.simulation.dsol.animation.d2.Renderable2d;
16  import nl.tudelft.simulation.naming.context.Contextualized;
17  
18  /**
19   * Extends {@code Renderable2d} to let the {@code contains} method look at the actual bounds shape, rather than only the box.
20   * <p>
21   * Copyright (c) 2024-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
22   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
23   * </p>
24   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
25   * @param <L> locatable type
26   */
27  public abstract class OtsRenderable<L extends OtsLocatable> extends Renderable2d<L>
28  {
29  
30      /** */
31      private static final long serialVersionUID = 20240223L;
32  
33      /** Standard rendering keys. */
34      public static Key[] RENDERING_KEYS = new Key[] {RenderingHints.KEY_ANTIALIASING};
35  
36      /** Standard rendering values. */
37      public static Object[] RENDERING_VALUES = new Object[] {RenderingHints.VALUE_ANTIALIAS_ON};
38  
39      /** Stored hints to reset. */
40      private static Map<Object, Object[]> OLD_RENDERING_HINTS = new WeakHashMap<>();
41  
42      /**
43       * Constructs a new Renderable2d.
44       * @param source T; the source
45       * @param contextProvider Contextualized; the object that can provide the context to store the animation objects
46       */
47      public OtsRenderable(final L source, final Contextualized contextProvider)
48      {
49          super(source, contextProvider);
50      }
51  
52      /**
53       * Set standard rendering hints for this renderable to paint. The graphics should be reset using {@code resetRendering()}
54       * after painting.
55       * @param graphics Graphics2D; graphics.
56       */
57      protected void setRendering(final Graphics2D graphics)
58      {
59          Object[] old = OLD_RENDERING_HINTS.computeIfAbsent(this, (o) -> new Object[RENDERING_KEYS.length]);
60          for (int i = 0; i < RENDERING_KEYS.length; i++)
61          {
62              old[i] = graphics.getRenderingHint(RENDERING_KEYS[i]);
63              graphics.setRenderingHint(RENDERING_KEYS[i], RENDERING_VALUES[i]);
64          }
65      }
66  
67      /**
68       * Resets rendering hints that this renderable changed through {@code setRendering()}.
69       * @param graphics Graphics2D; graphics.
70       */
71      protected void resetRendering(final Graphics2D graphics)
72      {
73          Object[] old = OLD_RENDERING_HINTS.computeIfAbsent(this, (o) -> new Object[RENDERING_KEYS.length]);
74          Throw.when(old == null, IllegalStateException.class,
75                  "Renderable %s resets rendering hints, but it never changed rendering hints with setRendering().", this);
76          for (int i = 0; i < RENDERING_KEYS.length; i++)
77          {
78              // If ever a null valued hint is used, just check for null values and do not reset the value in that case.
79              // For now the check is not implemented as no such hint is used.
80              // if (old[i] != null)
81              // {
82              graphics.setRenderingHint(RENDERING_KEYS[i], old[i]);
83              // }
84          }
85      }
86  
87      /** {@inheritDoc} */
88      @Override
89      public boolean contains(final Point2d pointWorldCoordinates, final Bounds2d extent)
90      {
91          Transform2d transformation = toBoundsTransform(getSource().getLocation());
92          Point2d pointObjectCoordinates = transformation.transform(pointWorldCoordinates);
93          return getSource().getBounds().contains(pointObjectCoordinates);
94      }
95  
96      /**
97       * Returns a transformation by which absolute coordinates can be translated and rotated to the frame of the possibly
98       * oriented location around which bounds are defined.
99       * @param location Point2d; location (can be an {@code Oriented}).
100      * @return Transform2d; transformation.
101      */
102     public static Transform2d toBoundsTransform(final Point2d location)
103     {
104         Transform2d transformation = new Transform2d();
105         if (location instanceof Oriented<?>)
106         {
107             transformation.rotation(-((Oriented<?>) location).getDirZ());
108         }
109         transformation.translate(-location.getX(), -location.getY());
110         return transformation;
111     }
112 
113 }