Passed
Push — master ( 82dbb2...0f24f3 )
by Seth
08:38
created

org.gannacademy.cdf.graphics.geom.Path   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 193
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 60
c 1
b 0
f 0
dl 0
loc 193
rs 10
wmc 20

16 Methods

Rating   Name   Duplication   Size   Complexity  
A Path(Shape,AffineTransform,DrawingPanel) 0 3 1
A Path(int,DrawingPanel) 0 6 2
A Path(int,int,DrawingPanel) 0 6 2
A Path(DrawingPanel) 0 6 2
A setShape(Shape) 0 6 2
A setHeight(double) 0 7 1
A lineTo(double,double) 0 2 1
A quadTo(double,double,double,double) 0 2 1
A setWidth(double) 0 7 1
A getShapeAsPath() 0 2 1
A curveTo(double,double,double,double,double,double) 0 2 1
A moveTo(double,double) 0 2 1
A setLocation(double,double) 0 3 1
A translate(double,double) 0 3 1
A closePath() 0 2 1
A transform(AffineTransform) 0 2 1
1
package org.gannacademy.cdf.graphics.geom;
2
3
import org.gannacademy.cdf.graphics.Drawable;
4
import org.gannacademy.cdf.graphics.DrawableException;
5
import org.gannacademy.cdf.graphics.ui.DrawingPanel;
6
7
import java.awt.*;
8
import java.awt.geom.AffineTransform;
9
import java.awt.geom.Path2D;
10
11
/**
12
 * <p>Draw an arbitrary path</p>
13
 *
14
 * <p><img src="doc-files/Path.png" alt="Path diagram"></p>
15
 *
16
 * <p>Paths are constructed from an arbitrary number of segments. Each segment is a {@link Line}, {@link QuadCurve}, or
17
 * {@link CubicCurve} drawn between the end point of the prior segment and new point. For example, in the diagram above,
18
 * the path is made of three segments:</p>
19
 *
20
 * <ol>
21
 * <li>A line from Point 1 to Point 2</li>
22
 * <li>A cubic curve from Point 2 to Point 3</li>
23
 * <li>A quadratic curve from Point 3 to Point 4</li>
24
 * </ol>
25
 *
26
 * <p>The path above could be drawn with the following sequence of instructions:</p>
27
 *
28
 * <pre>
29
 *   Path p = new Path();
30
 *   p.moveTo(20, 16); // Point 1
31
 *   p.lineTo(40, 140); // Point 2
32
 *   p.curveTo(
33
 *       100, 140, // first Bézier control point
34
 *       120, 127, // second Bézier control point
35
 *       120, 78 // Point 3
36
 *   );
37
 *   p.quadTo(
38
 *       120, 16; // quadratic control point
39
 *       220, 78 // Point 4
40
 *   );
41
 * </pre>
42
 *
43
 * <p>All paths start with empty geometry and are then built segment by segment using {@link #moveTo(double, double)},
44
 * {@link #lineTo(double, double)}, {@link #quadTo(double, double, double, double)}, and
45
 * {@link #curveTo(double, double, double, double, double, double)} methods to extend the existing geometry.</p>
46
 *
47
 * <p>Note that the first segment added to the path must be a {@link #moveTo(double, double)} instruction, to locate
48
 * the first point in the path. Additional {@link #moveTo(double, double)} calls may be made as the path is defined,
49
 * creating a discontinuous path.</p>
50
 *
51
 * <p>Paths are particularly complex (and therefore flexible and powerful!). As the underlying geometry of this object
52
 * is stored as a {@link Path2D}, it is worth perusing that documentation for information on details like approaches to
53
 * filling, stroking, or transforming paths. More detailed explanations of how the Bézier curve segments are computed
54
 * can be found in {@link QuadCurve} and {@link CubicCurve}.</p>
55
 *
56
 * @author <a href="https://github.com/gann-cdf/graphics/issues">Seth Battis</a>
57
 */
58
public class Path extends Drawable {
59
  /**
60
   * <p>Construct a path with empty geometry</p>
61
   *
62
   * @param drawingPanel on which to draw
63
   */
64
  public Path(DrawingPanel drawingPanel) {
65
    try {
66
      setShape(new Path2D.Double());
67
      setDrawingPanel(drawingPanel);
68
    } catch (DrawableException e) {
69
      e.printStackTrace();
0 ignored issues
show
Best Practice introduced by
Throwable.printStackTrace writes to the console which might not be available at runtime. Using a logger is preferred.
Loading history...
70
    }
71
  }
72
73
  /**
74
   * <p>Construct a path with empty geometry and a winding rule</p>
75
   *
76
   * @param windingRule  The <a href="https://en.wikipedia.org/wiki/Nonzero-rule#/media/File:Even-odd_and_non-zero_winding_fill_rules.png">
77
   *                     winding rule</a> to determine how to fill the shape
78
   * @param drawingPanel on which to draw
79
   */
80
  public Path(int windingRule, DrawingPanel drawingPanel) {
81
    try {
82
      setShape(new Path2D.Double(windingRule));
83
      setDrawingPanel(drawingPanel);
84
    } catch (DrawableException e) {
85
      e.printStackTrace();
0 ignored issues
show
Best Practice introduced by
Throwable.printStackTrace writes to the console which might not be available at runtime. Using a logger is preferred.
Loading history...
86
    }
87
  }
88
89
  /**
90
   * <p>Construct a path with empty geometry, a winding rule and expected number of segments</p>
91
   *
92
   * <p>The path will expand to contain as many segments as are added to it, but setting the initial capacity to your best
93
   * guess gains some small amount of efficiency in reducing resizing operations.</p>
94
   *
95
   * @param windingRule     The <a href="https://en.wikipedia.org/wiki/Nonzero-rule#/media/File:Even-odd_and_non-zero_winding_fill_rules.png">
96
   *                        winding rule</a> to determine how to fill the shape
97
   * @param initialCapacity Anticipated number of segments
98
   * @param drawingPanel    on which to draw
99
   */
100
  public Path(int windingRule, int initialCapacity, DrawingPanel drawingPanel) {
101
    try {
102
      setShape(new Path2D.Double(windingRule, initialCapacity));
103
      setDrawingPanel(drawingPanel);
104
    } catch (DrawableException e) {
105
      e.printStackTrace();
0 ignored issues
show
Best Practice introduced by
Throwable.printStackTrace writes to the console which might not be available at runtime. Using a logger is preferred.
Loading history...
106
    }
107
  }
108
109
  /**
110
   * <p>Construct a path from {@link Shape} geometry and a transformation</p>
111
   *
112
   * @param shape          of underlying geometry
113
   * @param transformation to apply {@code shape} (i.e. scale, translation, rotation)
114
   * @param drawingPanel   on which to draw
115
   * @throws DrawableException If {@code shape} cannot be converted to a {@link Path2D} (a highly unlikely eventuality)
116
   */
117
  public Path(Shape shape, AffineTransform transformation, DrawingPanel drawingPanel) throws DrawableException {
118
    setShape(new Path2D.Double(shape, transformation));
119
    setDrawingPanel(drawingPanel);
120
  }
121
122
  /**
123
   * Underlying {@link Path2D} geometry
124
   *
125
   * @return Underlying {@link Path2D} geometry
126
   */
127
  protected Path2D getShapeAsPath() {
128
    return (Path2D) getShape();
129
  }
130
131
  @Override
132
  public void setShape(Shape shape) throws DrawableException {
133
    if (shape instanceof Path2D) {
134
      super.setShape(shape);
135
    } else {
136
      throw new DrawableException("Attempt to set Path's underlying shape to a non-Path2D instance");
137
    }
138
  }
139
140
  @Override
141
  public void setWidth(double width) {
142
    // translate to origin before scaling so that distance from origin is not _also_ scaled!
143
    double x = getX();
144
    transform(AffineTransform.getTranslateInstance(-x, 0));
145
    transform(AffineTransform.getScaleInstance(width / getWidth(), 1));
146
    transform(AffineTransform.getTranslateInstance(x, 0));
147
  }
148
149
  @Override
150
  public void setHeight(double height) {
151
    // translate to origin before scaling so that distance from origin is not _also_ scaled!
152
    double y = getY();
153
    transform(AffineTransform.getTranslateInstance(0, -y));
154
    transform(AffineTransform.getScaleInstance(1, height / getHeight()));
155
    transform(AffineTransform.getTranslateInstance(0, y));
156
  }
157
158
  /**
159
   * <p>Add a cubic curve segment to the path</p>
160
   *
161
   * <p><img src="doc-files/CubicCurve.png" alt="Cubic Curve diagram"></p>
162
   *
163
   * <p>The cubic Bézier curve starts at the current end point of the path and extends through two control points. For
164
   * more details on how cubic Bézier curves are computes, refer to {@link CubicCurve}.</p>
165
   *
166
   * @param ctrlX1 X-coordinate of first control point
167
   * @param ctrlY1 Y-coordinate of first control point
168
   * @param ctrlX2 X-coordinate of second control point
169
   * @param ctrlY2 Y-coordinate of second control point
170
   * @param x3     X-coordinate of end point
171
   * @param y3     Y-coordinate of end point
172
   */
173
  public void curveTo(double ctrlX1, double ctrlY1, double ctrlX2, double ctrlY2, double x3, double y3) {
174
    getShapeAsPath().curveTo(ctrlX1, ctrlY1, ctrlX2, ctrlY2, x3, y3);
175
  }
176
177
  /**
178
   * <p>Add a line segment to the path</p>
179
   *
180
   * <p><img src="doc-files/Line.png" alt="Line diagram"></p>
181
   *
182
   * <p>The line starts at the current end point of the path. For more details on drawing lines, refer to {@link Line}.</p>
183
   *
184
   * @param x coordinate of end point
185
   * @param y coordinate of end point
186
   */
187
  public void lineTo(double x, double y) {
188
    getShapeAsPath().lineTo(x, y);
189
  }
190
191
  @Override
192
  public void translate(double dx, double dy) {
193
    getShapeAsPath().transform(AffineTransform.getTranslateInstance(dx, dy));
194
  }
195
196
  @Override
197
  public void setLocation(double x, double y) {
198
    translate(x - getX(), y - getY());
199
  }
200
201
  /**
202
   * <p>Select a new starting point for subsequent path segments</p>
203
   *
204
   * <p>Path segments are defined relative to the end point of the previous segment. {@code moveTo()}
205
   * must be the first instruction to the path to set a starting point for following segments. This method can also
206
   * be used to define a discontinuous path.</p>
207
   *
208
   * @param x coordinate
209
   * @param y coordinate
210
   */
211
  public void moveTo(double x, double y) {
212
    getShapeAsPath().moveTo(x, y);
213
  }
214
215
216
  /**
217
   * <p>Add a quadratic Bézier curve segment to the path</p>
218
   *
219
   * <p><img src="doc-files/QuadCurve.png" alt="Quad Curve diagram"></p>
220
   *
221
   * <p>The quadratic Bézier curve segments starts at the end point of the previous path segment, through a control
222
   * point to the end point. For more information on computing quadratic Bézier curves, refer to {@link QuadCurve}.</p>
223
   *
224
   * @param ctrlX1 X-coordinate of control point
225
   * @param ctrlY1 Y-coordinate of control point
226
   * @param x2     X-coordinate of end point
227
   * @param y2     Y-coordinate of end point
228
   */
229
  public void quadTo(double ctrlX1, double ctrlY1, double x2, double y2) {
230
    getShapeAsPath().quadTo(ctrlX1, ctrlY1, x2, y2);
231
  }
232
233
    /**
234
     * <p>Close the path by drawing a straight line back to the starting point</p>
235
     */
236
    public void closePath() {
237
        getShapeAsPath().closePath();
238
    }
239
240
  /**
241
   * <p>Transform the path</p>
242
   *
243
   * <p>An "affine transformation" is one in which the spatial relationships of the points of the path are not changed
244
   * relative to each other &mdash; scale, translation, and rotation. Refer to {@link AffineTransform} for more
245
   * information.</p>
246
   *
247
   * @param transformation to be applied to the path
248
   */
249
  public void transform(AffineTransform transformation) {
250
    getShapeAsPath().transform(transformation);
251
  }
252
}
253