Source code: com/arranger/jarl/stroke/base/Trace.java
1 package com.arranger.jarl.stroke.base;
2
3 import com.arranger.jarl.base.IContext;
4 import com.arranger.jarl.base.IJarlObjectInfo;
5 import com.arranger.jarl.stroke.BaseSegmentStroke;
6 import com.arranger.jarl.util.*;
7 import com.arranger.jarl.widget.IWidget;
8 import org.w3c.dom.Element;
9
10 import java.awt.*;
11 import java.awt.geom.Point2D;
12
13 /**
14 * Trace traces just a portion of the overall shape
15 *
16 *
17 * @strokeAttribute traceLength ## xs:string ## the percentage of the overall shape to trace (eg 5%)
18 * @strokeAttribute offset ## xs:string ## the percentage throgh the shape to start at (eg 0%)
19 * @strokeAttribute clockwise ## xs:boolean ## the direction to go (true|false)
20 *
21 * @strokeAttribute startTraceLength ## xs:string ## the starting percentage of the overall shape to trace (eg 5%)
22 * @strokeAttribute startOffset ## xs:string ## the starting percentage throgh the shape to start at (eg 0%)
23 * @strokeAttribute startClockwise ## xs:boolean ## the starting direction to go (true|false)
24 * @strokeAttribute endTraceLength ## xs:string ## the ending percentage of the overall shape to trace (eg 5%)
25 * @strokeAttribute endOffset ## xs:string ## the ending percentage throgh the shape to end at (eg 0%)
26 * @strokeAttribute endClockwise ## xs:boolean ## the ending direction to go (true|false)
27 */
28 public class Trace extends BaseSegmentStroke {
29
30 protected double m_traceLength = Double.NEGATIVE_INFINITY;
31 protected double m_offset = Double.NEGATIVE_INFINITY;
32 protected boolean m_clockwise = true;
33
34 protected double m_startTraceLength = Double.NEGATIVE_INFINITY;
35 protected double m_startOffset = Double.NEGATIVE_INFINITY;
36 protected boolean m_startClockwise = true;
37 protected double m_endTraceLength = Double.NEGATIVE_INFINITY;
38 protected double m_endOffset = Double.NEGATIVE_INFINITY;
39 protected boolean m_endClockwise = true;
40
41
42 /**
43 * This will perform custom drawing of the shape
44 * onto the graphics on behalf of the widget
45 *
46 * NB: this will need to interpolate points in between points
47 *
48 * @param widget
49 * @param context
50 * @param graphics2D
51 * @param shape
52 */
53 public Shape processShape(IWidget widget,
54 IContext context,
55 Graphics2D graphics2D,
56 Shape shape) {
57
58 double currentWidgetPct = InterpolateUtil.interpolate(widget, context.getTime());
59 double currentTraceLength;
60 double currentOffset;
61 boolean currentClockwise;
62
63 if (m_traceLength != Double.NEGATIVE_INFINITY) {
64 currentTraceLength = m_traceLength;
65 } else if (m_startTraceLength != Double.NEGATIVE_INFINITY && m_endTraceLength != Double.NEGATIVE_INFINITY) {
66 currentTraceLength = InterpolateUtil.interpolate(0,
67 1,
68 m_startTraceLength,
69 m_endTraceLength,
70 currentWidgetPct);
71 } else {
72 TraceConfigSegment traceConfigSegment = (TraceConfigSegment) getCurrentSegment(widget, context);
73 double currentSegmentWidgetPct = getCurrentSegmentTimePct(widget, context, traceConfigSegment);
74 currentTraceLength = InterpolateUtil.interpolate(0,
75 1,
76 traceConfigSegment.getStartTraceLength(),
77 traceConfigSegment.getEndTraceLength(),
78 currentSegmentWidgetPct);
79 }
80
81 //base the clockwise off of offset
82 if (m_offset != Double.NEGATIVE_INFINITY) {
83 currentOffset = m_offset;
84 currentClockwise = m_clockwise;
85 } else if (m_startOffset != Double.NEGATIVE_INFINITY && m_endOffset != Double.NEGATIVE_INFINITY) {
86 currentOffset = InterpolateUtil.interpolate(0,
87 360,
88 m_startOffset,
89 m_endOffset,
90 currentWidgetPct);
91 currentClockwise = m_startClockwise;
92 } else {
93 TraceConfigSegment traceConfigSegment = (TraceConfigSegment) getCurrentSegment(widget, context);
94 double currentSegmentWidgetPct = getCurrentSegmentTimePct(widget, context, traceConfigSegment);
95 currentOffset = InterpolateUtil.interpolate(0,
96 1,
97 traceConfigSegment.getStartOffset(),
98 traceConfigSegment.getEndOffset(),
99 currentSegmentWidgetPct);
100 currentClockwise = traceConfigSegment.isStartClockwise();
101 }
102 currentOffset %= 1.0; //normalize
103 Point2D[] points = WidgetUtil.transform(shape,
104 new TraceWidgetTransform(currentTraceLength,
105 currentOffset,
106 currentClockwise,
107 context));
108 return WidgetUtil.pointsToShape(points, false);
109 }
110
111 /**
112 * Always remember some attrs might not be there
113 * @param context
114 */
115 protected void initAttributes(IContext context) {
116 super.initAttributes(context);
117
118 ObjectUtil.initializeField("traceLength", m_configElement, this, ObjectUtil.NORMALIZING_CONVERSION);
119 ObjectUtil.initializeField("clockwise", m_configElement, this, ObjectUtil.PRIMITIVE_CONVERSION);
120 ObjectUtil.initializeField("offset", m_configElement, this, ObjectUtil.NORMALIZING_CONVERSION);
121
122 ObjectUtil.initializeField("startTraceLength", m_configElement, this, ObjectUtil.NORMALIZING_CONVERSION);
123 ObjectUtil.initializeField("startClockwise", m_configElement, this, ObjectUtil.PRIMITIVE_CONVERSION);
124 ObjectUtil.initializeField("startOffset", m_configElement, this, ObjectUtil.NORMALIZING_CONVERSION);
125 ObjectUtil.initializeField("endTraceLength", m_configElement, this, ObjectUtil.NORMALIZING_CONVERSION);
126 ObjectUtil.initializeField("endClockwise", m_configElement, this, ObjectUtil.PRIMITIVE_CONVERSION);
127 ObjectUtil.initializeField("endOffset", m_configElement, this, ObjectUtil.NORMALIZING_CONVERSION);
128 }
129
130 /**
131 * Override this, and for every field that you're using, call {@link #populateInfo}
132 * for example:
133 * <code>
134 * populateInfo(jarlObjectInfo, "zOrder", "Z-Order", JarlInfoUtil.PRIMITIVE_DISPLAY);
135 * </code>
136 *
137 * @param jarlObjectInfo
138 *
139 * @see JarlInfoUtil#PRIMITIVE_DISPLAY
140 * @see #populateInfo
141 * @see ObjectUtil#initializeField
142 */
143 protected void addJarlObjectInfo(IJarlObjectInfo jarlObjectInfo) {
144 super.addJarlObjectInfo(jarlObjectInfo);
145 populateInfo(jarlObjectInfo, "traceLength", "Trace Length", JarlInfoUtil.PCT_DISPLAY);
146 populateInfo(jarlObjectInfo, "clockwise", "Clockwise", JarlInfoUtil.PRIMITIVE_DISPLAY);
147 populateInfo(jarlObjectInfo, "offset", "Offset", JarlInfoUtil.PCT_DISPLAY);
148
149 populateInfo(jarlObjectInfo, "startTraceLength", "Start Trace Length", JarlInfoUtil.PCT_DISPLAY);
150 populateInfo(jarlObjectInfo, "endTraceLength", "End Trace Length", JarlInfoUtil.PCT_DISPLAY);
151 populateInfo(jarlObjectInfo, "startClockwise", "Start Clockwise", JarlInfoUtil.PRIMITIVE_DISPLAY);
152 populateInfo(jarlObjectInfo, "endClockwise", "End Clockwise", JarlInfoUtil.PRIMITIVE_DISPLAY);
153 populateInfo(jarlObjectInfo, "startOffset", "Start Offset", JarlInfoUtil.PCT_DISPLAY);
154 populateInfo(jarlObjectInfo, "endOffset", "End Offset", JarlInfoUtil.PCT_DISPLAY);
155 }
156
157 /**
158 * Create a concrete {@link WidgetConfigSegment} based on this element
159 * @param element
160 * @return a specific {@link WidgetConfigSegment}
161 */
162 public WidgetConfigSegment createSegment(Element element) {
163 return new TraceConfigSegment(element);
164 }
165
166 protected static class TraceConfigSegment extends WidgetConfigSegment {
167
168 protected double m_startTraceLength = Double.NEGATIVE_INFINITY;
169 protected double m_startOffset = Double.NEGATIVE_INFINITY;
170 protected boolean m_startClockwise = true;
171 protected double m_endTraceLength = Double.NEGATIVE_INFINITY;
172 protected double m_endOffset = Double.NEGATIVE_INFINITY;
173 protected boolean m_endClockwise = true;
174
175 public TraceConfigSegment(Element element) {
176 super(element);
177
178 ObjectUtil.initializeFieldStrict("startTraceLength", element, this, ObjectUtil.NORMALIZING_CONVERSION);
179 ObjectUtil.initializeFieldStrict("startClockwise", element, this, ObjectUtil.PRIMITIVE_CONVERSION);
180 ObjectUtil.initializeFieldStrict("startOffset", element, this, ObjectUtil.NORMALIZING_CONVERSION);
181 ObjectUtil.initializeFieldStrict("endTraceLength", element, this, ObjectUtil.NORMALIZING_CONVERSION);
182 ObjectUtil.initializeFieldStrict("endClockwise", element, this, ObjectUtil.PRIMITIVE_CONVERSION);
183 ObjectUtil.initializeFieldStrict("endOffset", element, this, ObjectUtil.NORMALIZING_CONVERSION);
184 }
185
186
187 protected void addJarlObjectInfo(IJarlObjectInfo jarlObjectInfo) {
188 super.addJarlObjectInfo(jarlObjectInfo);
189 populateInfo(jarlObjectInfo, "startTraceLength", "Start Trace Length", JarlInfoUtil.PCT_DISPLAY);
190 populateInfo(jarlObjectInfo, "endTraceLength", "End Trace Length", JarlInfoUtil.PCT_DISPLAY);
191 populateInfo(jarlObjectInfo, "startClockwise", "Start Clockwise", JarlInfoUtil.PRIMITIVE_DISPLAY);
192 populateInfo(jarlObjectInfo, "endClockwise", "End Clockwise", JarlInfoUtil.PRIMITIVE_DISPLAY);
193 populateInfo(jarlObjectInfo, "startOffset", "Start Offset", JarlInfoUtil.PCT_DISPLAY);
194 populateInfo(jarlObjectInfo, "endOffset", "End Offset", JarlInfoUtil.PCT_DISPLAY);
195 }
196
197 public double getStartTraceLength() {
198 return m_startTraceLength;
199 }
200
201 public double getStartOffset() {
202 return m_startOffset;
203 }
204
205 public boolean isStartClockwise() {
206 return m_startClockwise;
207 }
208
209 public double getEndTraceLength() {
210 return m_endTraceLength;
211 }
212
213 public double getEndOffset() {
214 return m_endOffset;
215 }
216
217 public boolean isEndClockwise() {
218 return m_endClockwise;
219 }
220 }
221
222 public static class TraceWidgetTransform implements IWidgetTransform {
223 protected double m_currentTraceLength;
224 protected double m_currentOffset;
225 protected boolean m_currentClockwise;
226 protected double m_currentHigh;
227 protected IContext m_currentContext;
228
229 public TraceWidgetTransform(double currentTraceLength,
230 double currentOffset,
231 boolean currentClockwise,
232 IContext currentContext) {
233 m_currentTraceLength = currentTraceLength;
234 m_currentOffset = currentOffset;
235 m_currentClockwise = currentClockwise;
236 m_currentContext = currentContext;
237 }
238
239 /**
240 * Initialize the transform, returning the offset if needed
241 *
242 * @param shapePoints the original shape points
243 * @return the offset into the array of points. return 0 for no offset
244 */
245 public double initializeTransform(Point2D[] shapePoints) {
246 //need to figure out the offset and the length boundaries
247 double currentTotalDistance = 0;
248 for (int sourceIndex = 0; sourceIndex < shapePoints.length - 1; sourceIndex++) {
249 double x1 = shapePoints[sourceIndex].getX();
250 double y1 = shapePoints[sourceIndex].getY();
251 double x2 = shapePoints[sourceIndex + 1].getX();
252 double y2 = shapePoints[sourceIndex + 1].getY();
253
254 currentTotalDistance += Point2D.distance(x1, y1, x2, y2);
255 }
256
257 m_currentHigh = currentTotalDistance * m_currentTraceLength;
258
259 /*m_currentContext.onStatus("totalDistance: " + m_currentTotalDistance);
260 m_currentContext.onStatus("currentHigh: " + m_currentHigh);*/
261
262 return m_currentOffset * shapePoints.length;
263 }
264
265
266 /**
267 * Generate a series of points that will be used to transform
268 * other lines
269 *
270 * @param distance the total x requested (pt[N].x - pt[0].x == x)
271 * @param offset an offset into the internal algortihm
272 * @param precision how frequent to get points
273 * @return an arry of points of the form: x1, y1, x2, y2, .... xN, yN
274 */
275 public double[] getPoints(double distance, double offset, double precision) {
276 //only return points when the request (distance + offset) is within the currentStart and currentEnd
277 double testPoint = distance + offset;
278
279 if (testPoint >= m_currentHigh) {
280 if (offset >= m_currentHigh) {
281 return new double[0];
282 } else {
283 distance = m_currentHigh - offset;
284 }
285 }
286
287 double[] points = new double[(int) ((distance / precision) + 1) * 2];
288 int writeIndex = 0;
289 for (double x = 0; x <= distance; x += precision) {
290 points[writeIndex++] = x;
291 points[writeIndex++] = 0;
292 }
293
294 if (writeIndex != points.length) {
295 double[] filteredPoints = new double[writeIndex];
296 System.arraycopy(points, 0, filteredPoints, 0, writeIndex);
297 points = filteredPoints;
298 }
299
300 return points;
301 }
302
303 /**
304 * Whether or not to reverse through the points
305 * @return true for reversing, false for normal
306 */
307 public boolean isReverse() {
308 return !m_currentClockwise;
309 }
310 }
311 }