JOGL v2.6.0-rc-20250706
JOGL, High-Performance Graphics Binding for Java™ (public API).
Path2F.java
Go to the documentation of this file.
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17/**
18 * @author Denis M. Kishenko
19 * @author Sven Gothel
20 */
21package com.jogamp.math.geom.plane;
22
23import java.io.PrintStream;
24import java.util.NoSuchElementException;
25
26import com.jogamp.math.geom.AABBox;
27
28/**
29 * Path2F represents and provides construction method for a 2D shape using float[2] points.
30 */
31public final class Path2F {
32 static final String invalidWindingRuleValue = "Invalid winding rule value";
33 static final String iteratorOutOfBounds = "Iterator out of bounds";
34
35 /** A Path2D segment type */
36 public static enum SegmentType {
42
43 /** Number of points associated with this segment type */
44 public final int point_count;
45
46 /** Return the integer segment type value as a byte */
47 public byte integer() {
48 return (byte) this.ordinal();
49 }
50
51 /** Return the SegmentType associated with the integer segment type */
52 public static SegmentType valueOf(final int type) {
53 switch( type ) {
54 case 0: return MOVETO;
55 case 1: return LINETO;
56 case 2: return QUADTO;
57 case 3: return CUBICTO;
58 case 4: return CLOSE;
59 default:
60 throw new IllegalArgumentException("Unhandled Segment Type: "+type);
61 }
62 }
63
64 /** Return the number of points associated with the integer segment type */
65 public static int getPointCount(final int type) {
66 switch( type ) {
67 case 0: return MOVETO.point_count;
68 case 1: return LINETO.point_count;
69 case 2: return QUADTO.point_count;
70 case 3: return CUBICTO.point_count;
71 case 4: return CLOSE.point_count;
72 default:
73 throw new IllegalArgumentException("Unhandled Segment Type: "+type);
74 }
75 }
76
77 SegmentType(final int v) {
78 this.point_count = v;
79 }
80 }
81
82 /**
83 * The buffers size
84 */
85 private static final int BUFFER_SIZE = 10;
86
87 /**
88 * The buffers capacity
89 */
90 private static final int BUFFER_CAPACITY = 10;
91
92 /**
93 * The point's types buffer
94 */
95 private byte[] m_types;
96
97 /**
98 * The points buffer
99 */
100 private float[] m_points;
101
102 /**
103 * The point's type buffer size
104 */
105 private int m_typeSize;
106
107 /**
108 * The points buffer size
109 */
110 private int m_pointSize;
111
112 /**
113 * The winding path rule
114 */
115 private WindingRule m_rule;
116
117 /*
118 * GeneralPath path iterator
119 */
120 public static final class Iterator {
121
122 /**
123 * The source GeneralPath object
124 */
125 private final Path2F p;
126
127 /**
128 * The path iterator transformation
129 */
130 private final AffineTransform t;
131
132 /**
133 * The current cursor position in types buffer
134 */
135 private int typeIndex;
136
137 /**
138 * The current cursor position in points buffer
139 */
140 private int pointIndex;
141
142 /**
143 * Constructs a new GeneralPath.Iterator for given general path
144 * @param path - the source GeneralPath object
145 */
146 Iterator(final Path2F path) {
147 this(path, null);
148 }
149
150 /**
151 * Constructs a new GeneralPath.Iterator for given general path and transformation
152 * @param path - the source GeneralPath object
153 * @param at - the AffineTransform object to apply rectangle path
154 */
155 public Iterator(final Path2F path, final AffineTransform at) {
156 this.p = path;
157 this.t = at;
158 reset();
159 }
160
161 private void reset() {
162 typeIndex = 0;
163 pointIndex = 0;
164 }
165
166 /** Return the {@link WindingRule} set */
168 return p.getWindingRule();
169 }
170
171 /**
172 * Compute the general winding of the vertices
173 * @return CCW or CW {@link Winding}
174 */
176 return area() >= 0 ? Winding.CCW : Winding.CW ;
177 }
178
179 /** Returns reference of the point array covering the whole iteration of Path2D, use {@link #index()} to access the current point. */
180 public float[] points() { return p.m_points; }
181
182 /** Return the {@link #points()} index for the current segment. */
183 public int index() { return pointIndex; }
184
185 /** Return current segment type */
186 public SegmentType getType() { return SegmentType.valueOf( p.m_types[typeIndex] ); }
187
188 /**
189 * Return the current segment type and copies the current segment's points to given storage
190 * @param coords storage for current segment's points
191 * @return current segment type
192 * @see #points()
193 * @see #type_index()
194 * @see #getType()
195 * @deprecated try to use {@link #index()}, {@link #points()} and {@link #next()} to avoid copying
196 */
197 @Deprecated
198 public SegmentType currentSegment(final float[] coords) {
199 if (!hasNext()) {
200 throw new NoSuchElementException(iteratorOutOfBounds);
201 }
202 final SegmentType type = getType();
203 final int count = type.point_count;
204 System.arraycopy(p.m_points, pointIndex, coords, 0, count*2);
205 if (t != null) {
206 t.transform(coords, 0, coords, 0, count);
207 }
208 return type;
209 }
210
211 /** Returns true if the iteration has more elements. */
212 public boolean hasNext() {
213 return typeIndex < p.m_typeSize;
214 }
215
216 /** Returns the current segment type in the iteration, then moving to the next path segment. */
217 public SegmentType next() {
218 final SegmentType t = getType();
219 pointIndex += 2 * t.point_count;
220 ++typeIndex;
221 return t;
222 }
223
224 /**
225 * Computes the area of the path to check if ccw
226 * @return positive area if ccw else negative area value
227 */
228 private float area() {
229 float area = 0.0f;
230 final float[] points = points();
231 final float[] pCoord = new float[2];
232 while ( hasNext() ) {
233 final int idx = index();
234 final SegmentType type = next();
235 switch ( type ) {
236 case MOVETO:
237 pCoord[0] = points[idx+0];
238 pCoord[1] = points[idx+1];
239 break;
240 case LINETO:
241 area += pCoord[0] * points[idx+1] - points[idx+0] * pCoord[1];
242 pCoord[0] = points[idx+0];
243 pCoord[1] = points[idx+1];
244 break;
245 case QUADTO:
246 area += pCoord[0] * points[idx+1] - points[idx+0] * pCoord[1];
247 area += points[idx+0] * points[idx+3] - points[idx+2] * points[idx+1];
248 pCoord[0] = points[idx+2];
249 pCoord[1] = points[idx+3];
250 break;
251 case CUBICTO:
252 area += pCoord[0] * points[idx+1] - points[idx+0] * pCoord[1];
253 area += points[idx+0] * points[idx+3] - points[idx+2] * points[idx+1];
254 area += points[idx+2] * points[idx+5] - points[idx+4] * points[idx+3];
255 pCoord[0] = points[idx+4];
256 pCoord[1] = points[idx+5];
257 break;
258 case CLOSE:
259 break;
260 }
261 }
262 reset();
263 return area;
264 }
265 }
266
267 public Path2F() {
268 this(WindingRule.NON_ZERO, BUFFER_SIZE, BUFFER_SIZE);
269 }
270
271 public Path2F(final WindingRule rule) {
272 this(rule, BUFFER_SIZE, BUFFER_SIZE);
273 }
274
275 public Path2F(final WindingRule rule, final int initialCapacity) {
276 this(rule, initialCapacity, initialCapacity);
277 }
278
279 public Path2F(final WindingRule rule, final int initialTypeCapacity, final int initialPointCapacity) {
280 setWindingRule(rule);
281 m_types = new byte[initialTypeCapacity];
282 m_points = new float[initialPointCapacity * 2];
283 }
284
285 public Path2F(final Path2F path) {
286 this(WindingRule.NON_ZERO, BUFFER_SIZE);
287 final Iterator p = path.iterator(null);
289 append(p, false);
290 }
291
292 /** Set the {@link WindingRule} set */
293 public void setWindingRule(final WindingRule rule) {
294 this.m_rule = rule;
295 }
296
297 /** Return the {@link WindingRule} set */
299 return m_rule;
300 }
301
302 /**
303 * Checks points and types buffer size to add pointCount points. If necessary realloc buffers to enlarge size.
304 * @param pointCount - the point count to be added in buffer
305 */
306 private void checkBuf(final int pointCount, final boolean checkMove) {
307 if (checkMove && m_typeSize == 0) {
308 throw new IllegalPathStateException("First segment should be SEG_MOVETO type");
309 }
310 if (m_typeSize == m_types.length) {
311 final byte tmp[] = new byte[m_typeSize + BUFFER_CAPACITY];
312 System.arraycopy(m_types, 0, tmp, 0, m_typeSize);
313 m_types = tmp;
314 }
315 if (m_pointSize + pointCount > m_points.length) {
316 final float tmp[] = new float[m_pointSize + Math.max(BUFFER_CAPACITY * 2, pointCount)];
317 System.arraycopy(m_points, 0, tmp, 0, m_pointSize);
318 m_points = tmp;
319 }
320 }
321
322 /**
323 * Start a new position for the next line segment at given point x/y (P1).
324 * @param x point (P1)
325 * @param y point (P1)
326 */
327 public void moveTo(final float x, final float y) {
328 if ( m_typeSize > 0 && m_types[m_typeSize - 1] == SegmentType.MOVETO.integer() ) {
329 m_points[m_pointSize - 2] = x;
330 m_points[m_pointSize - 1] = y;
331 } else {
332 checkBuf(2, false);
333 m_types[m_typeSize++] = SegmentType.MOVETO.integer();
334 m_points[m_pointSize++] = x;
335 m_points[m_pointSize++] = y;
336 }
337 }
338
339 /**
340 * Add a line segment, intersecting the last point and the given point x/y (P1).
341 * @param x final point (P1)
342 * @param y final point (P1)
343 */
344 public void lineTo(final float x, final float y) {
345 checkBuf(2, true);
346 m_types[m_typeSize++] = SegmentType.LINETO.integer();
347 m_points[m_pointSize++] = x;
348 m_points[m_pointSize++] = y;
349 }
350
351 /**
352 * Add a quadratic curve segment, intersecting the last point and the second given point x2/y2 (P2).
353 * @param x1 quadratic parametric control point (P1)
354 * @param y1 quadratic parametric control point (P1)
355 * @param x2 final interpolated control point (P2)
356 * @param y2 final interpolated control point (P2)
357 */
358 public void quadTo(final float x1, final float y1, final float x2, final float y2) {
359 checkBuf(4, true);
360 m_types[m_typeSize++] = SegmentType.QUADTO.integer();
361 m_points[m_pointSize++] = x1;
362 m_points[m_pointSize++] = y1;
363 m_points[m_pointSize++] = x2;
364 m_points[m_pointSize++] = y2;
365 }
366
367 /**
368 * Add a cubic Bézier curve segment, intersecting the last point and the second given point x3/y3 (P3).
369 * @param x1 Bézier control point (P1)
370 * @param y1 Bézier control point (P1)
371 * @param x2 Bézier control point (P2)
372 * @param y2 Bézier control point (P2)
373 * @param x3 final interpolated control point (P3)
374 * @param y3 final interpolated control point (P3)
375 */
376 public void cubicTo(final float x1, final float y1, final float x2, final float y2, final float x3, final float y3) {
377 checkBuf(6, true);
378 m_types[m_typeSize++] = SegmentType.CUBICTO.integer();
379 m_points[m_pointSize++] = x1;
380 m_points[m_pointSize++] = y1;
381 m_points[m_pointSize++] = x2;
382 m_points[m_pointSize++] = y2;
383 m_points[m_pointSize++] = x3;
384 m_points[m_pointSize++] = y3;
385 }
386
387 /**
388 * Closes the current sub-path segment by drawing a straight line back to the coordinates of the last moveTo. If the path is already closed then this method has no effect.
389 */
390 public void closePath() {
391 if (!isClosed()) {
392 checkBuf(0, true);
393 m_types[m_typeSize++] = SegmentType.CLOSE.integer();
394 }
395 }
396
397 final public int size() {
398 return m_typeSize;
399 }
400
401 /**
402 * Returns true if the last sub-path is closed, otherwise false.
403 */
404 final public boolean isClosed() {
405 return m_typeSize > 0 && m_types[m_typeSize - 1] == SegmentType.CLOSE.integer() ;
406 }
407
408 /**
409 * Compute the general winding of the vertices
410 * @param vertices array of Vertices
411 * @return CCW or CW {@link Winding}
412 */
414 return iterator(null).getWinding();
415 }
416
417 @Override
418 public String toString() {
419 return "[size "+size()+", closed "+isClosed()+", winding[rule "+getWindingRule()+", "+getWinding()+"]]";
420 }
421
422 /**
423 * Append the given path geometry to this instance
424 * @param path the {@link Path2F} to append to this instance
425 * @param connect pass true to turn an initial moveTo segment into a lineTo segment to connect the new geometry to the existing path, otherwise pass false.
426 */
427 public void append(final Path2F path, final boolean connect) {
428 append(path.iterator(null), connect);
429 }
430
431 /**
432 * Append the given path geometry to this instance
433 * @param path the {@link Path2F.Iterator} to append to this instance
434 * @param connect pass true to turn an initial moveTo segment into a lineTo segment to connect the new geometry to the existing path, otherwise pass false.
435 */
436 public void append(final Iterator path, boolean connect) {
437 final float[] points = path.points();
438 while ( path.hasNext() ) {
439 final int idx = path.index();
440 final SegmentType type = path.next();
441 switch ( type ) {
442 case MOVETO:
443 if ( !connect || 0 == m_typeSize ) {
444 moveTo(points[idx+0], points[idx+1]);
445 break;
446 }
447 if ( m_types[m_typeSize - 1] != SegmentType.CLOSE.integer() &&
448 m_points[m_pointSize - 2] == points[idx+0] &&
449 m_points[m_pointSize - 1] == points[idx+1]
450 )
451 {
452 break;
453 }
454 // fallthrough: MOVETO -> LINETO
455 case LINETO:
456 lineTo(points[idx+0], points[idx+1]);
457 break;
458 case QUADTO:
459 quadTo(points[idx+0], points[idx+1], points[idx+2], points[idx+3]);
460 break;
461 case CUBICTO:
462 cubicTo(points[idx+0], points[idx+1], points[idx+2], points[idx+3], points[idx+4], points[idx+5]);
463 break;
464 case CLOSE:
465 closePath();
466 break;
467 }
468 connect = false;
469 }
470 }
471
472 public void printSegments(final PrintStream out) {
473 final Iterator path = iterator();
474 final float[] points = path.points();
475 int i = 0;
476 while ( path.hasNext() ) {
477 final int idx = path.index();
478 final SegmentType type = path.next();
479 switch ( type ) {
480 case MOVETO:
481 out.printf("%2d: moveTo(%.4f/%.4f)%n", i, points[idx+0], points[idx+1]);
482 break;
483 case LINETO:
484 out.printf("%2d: lineTo(%.4f/%.4f)%n", i, points[idx+0], points[idx+1]);
485 break;
486 case QUADTO:
487 out.printf("%2d: quadTo(%.4f/%.4f, %.4f/%.4f)%n", i, points[idx+0], points[idx+1], points[idx+2], points[idx+3]);
488 break;
489 case CUBICTO:
490 out.printf("%2d: cubicTo(%.4f/%.4f, %.4f/%.4f, %.4f/%.4f)%n", i, points[idx+0], points[idx+1], points[idx+2], points[idx+3], points[idx+4], points[idx+5]);
491 break;
492 case CLOSE:
493 out.printf("%2d: closePath()%n", i);
494 break;
495 }
496 ++i;
497 }
498 }
499
500 public void reset() {
501 m_typeSize = 0;
502 m_pointSize = 0;
503 }
504
505 public void transform(final AffineTransform t) {
506 t.transform(m_points, 0, m_points, 0, m_pointSize / 2);
507 }
508
510 final Path2F p = new Path2F(this);
511 if (t != null) {
512 p.transform(t);
513 }
514 return p;
515 }
516
517 public final synchronized AABBox getBounds2D() {
518 float rx1, ry1, rx2, ry2;
519 if (m_pointSize == 0) {
520 rx1 = ry1 = rx2 = ry2 = 0.0f;
521 } else {
522 int i = m_pointSize - 1;
523 ry1 = ry2 = m_points[i--];
524 rx1 = rx2 = m_points[i--];
525 while (i > 0) {
526 final float y = m_points[i--];
527 final float x = m_points[i--];
528 if (x < rx1) {
529 rx1 = x;
530 } else
531 if (x > rx2) {
532 rx2 = x;
533 }
534 if (y < ry1) {
535 ry1 = y;
536 } else
537 if (y > ry2) {
538 ry2 = y;
539 }
540 }
541 }
542 return new AABBox(rx1, ry1, 0f, rx2, ry2, 0f);
543 }
544
545 /**
546 * Checks cross count according to path rule to define is it point inside shape or not.
547 * @param cross - the point cross count
548 * @return true if point is inside path, or false otherwise
549 */
550 boolean isInside(final int cross) {
551 if (m_rule == WindingRule.NON_ZERO) {
552 return Crossing2F.isInsideNonZero(cross);
553 }
554 return Crossing2F.isInsideEvenOdd(cross);
555 }
556
557 public boolean contains(final float px, final float py) {
558 return isInside(Crossing2F.crossShape(this, px, py));
559 }
560
561 public boolean contains(final float rx, final float ry, final float rw, final float rh) {
562 final int cross = Crossing2F.intersectShape(this, rx, ry, rw, rh);
563 return cross != Crossing2F.CROSSING && isInside(cross);
564 }
565
566 public boolean intersects(final float rx, final float ry, final float rw, final float rh) {
567 final int cross = Crossing2F.intersectShape(this, rx, ry, rw, rh);
568 return cross == Crossing2F.CROSSING || isInside(cross);
569 }
570
571 public boolean contains(final AABBox r) {
572 return contains(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight());
573 }
574
575 public boolean intersects(final AABBox r) {
576 return intersects(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight());
577 }
578
580 return new Iterator(this);
581 }
582
584 return new Iterator(this, t);
585 }
586
587 /* public Path2F.Iterator getPathIterator(AffineTransform t, float flatness) {
588 return new FlatteningPathIterator(getPathIterator(t), flatness);
589 } */
590}
591
Axis Aligned Bounding Box.
Definition: AABBox.java:54
final float getWidth()
Definition: AABBox.java:879
final float getHeight()
Definition: AABBox.java:883
final AABBox transform(final AABBox src, final AABBox dst)
int index()
Return the points() index for the current segment.
Definition: Path2F.java:183
float[] points()
Returns reference of the point array covering the whole iteration of Path2D, use index() to access th...
Definition: Path2F.java:180
WindingRule getWindingRule()
Return the WindingRule set.
Definition: Path2F.java:167
Iterator(final Path2F path, final AffineTransform at)
Constructs a new GeneralPath.Iterator for given general path and transformation.
Definition: Path2F.java:155
Winding getWinding()
Compute the general winding of the vertices.
Definition: Path2F.java:175
SegmentType getType()
Return current segment type.
Definition: Path2F.java:186
boolean hasNext()
Returns true if the iteration has more elements.
Definition: Path2F.java:212
SegmentType next()
Returns the current segment type in the iteration, then moving to the next path segment.
Definition: Path2F.java:217
SegmentType currentSegment(final float[] coords)
Return the current segment type and copies the current segment's points to given storage.
Definition: Path2F.java:198
Path2F represents and provides construction method for a 2D shape using float[2] points.
Definition: Path2F.java:31
void moveTo(final float x, final float y)
Start a new position for the next line segment at given point x/y (P1).
Definition: Path2F.java:327
void append(final Iterator path, boolean connect)
Append the given path geometry to this instance.
Definition: Path2F.java:436
WindingRule getWindingRule()
Return the WindingRule set.
Definition: Path2F.java:298
Path2F(final WindingRule rule)
Definition: Path2F.java:271
void quadTo(final float x1, final float y1, final float x2, final float y2)
Add a quadratic curve segment, intersecting the last point and the second given point x2/y2 (P2).
Definition: Path2F.java:358
Path2F(final Path2F path)
Definition: Path2F.java:285
Path2F(final WindingRule rule, final int initialTypeCapacity, final int initialPointCapacity)
Definition: Path2F.java:279
Path2F createTransformedShape(final AffineTransform t)
Definition: Path2F.java:509
Path2F(final WindingRule rule, final int initialCapacity)
Definition: Path2F.java:275
void printSegments(final PrintStream out)
Definition: Path2F.java:472
boolean contains(final float px, final float py)
Definition: Path2F.java:557
void closePath()
Closes the current sub-path segment by drawing a straight line back to the coordinates of the last mo...
Definition: Path2F.java:390
final synchronized AABBox getBounds2D()
Definition: Path2F.java:517
boolean intersects(final float rx, final float ry, final float rw, final float rh)
Definition: Path2F.java:566
Winding getWinding()
Compute the general winding of the vertices.
Definition: Path2F.java:413
void transform(final AffineTransform t)
Definition: Path2F.java:505
void append(final Path2F path, final boolean connect)
Append the given path geometry to this instance.
Definition: Path2F.java:427
void cubicTo(final float x1, final float y1, final float x2, final float y2, final float x3, final float y3)
Add a cubic Bézier curve segment, intersecting the last point and the second given point x3/y3 (P3).
Definition: Path2F.java:376
void lineTo(final float x, final float y)
Add a line segment, intersecting the last point and the given point x/y (P1).
Definition: Path2F.java:344
void setWindingRule(final WindingRule rule)
Set the WindingRule set.
Definition: Path2F.java:293
Iterator iterator(final AffineTransform t)
Definition: Path2F.java:583
boolean contains(final float rx, final float ry, final float rw, final float rh)
Definition: Path2F.java:561
final boolean isClosed()
Returns true if the last sub-path is closed, otherwise false.
Definition: Path2F.java:404
boolean intersects(final AABBox r)
Definition: Path2F.java:575
boolean contains(final AABBox r)
Definition: Path2F.java:571
static int getPointCount(final int type)
Return the number of points associated with the integer segment type.
Definition: Path2F.java:65
final int point_count
Number of points associated with this segment type.
Definition: Path2F.java:44
static SegmentType valueOf(final int type)
Return the SegmentType associated with the integer segment type.
Definition: Path2F.java:52
byte integer()
Return the integer segment type value as a byte.
Definition: Path2F.java:47
Winding rule, either EVEN_ODD or NON_ZERO (like for TrueType fonts).
Definition: WindingRule.java:6
NON_ZERO
The non-zero rule specifies that a point lies inside the path if a ray drawn in any direction from th...
Winding direction, either clockwise (CW) or counter-clockwise (CCW).
Definition: Winding.java:6