JOGL v2.6.0-rc-20250712
JOGL, High-Performance Graphics Binding for Java™ (public API).
Outline.java
Go to the documentation of this file.
1/**
2 * Copyright 2010-2024 JogAmp Community. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without modification, are
5 * permitted provided that the following conditions are met:
6 *
7 * 1. Redistributions of source code must retain the above copyright notice, this list of
8 * conditions and the following disclaimer.
9 *
10 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 * of conditions and the following disclaimer in the documentation and/or other materials
12 * provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 *
24 * The views and conclusions contained in the software and documentation are those of the
25 * authors and should not be interpreted as representing official policies, either expressed
26 * or implied, of JogAmp Community.
27 */
28package com.jogamp.graph.geom;
29
30import java.io.PrintStream;
31import java.util.ArrayList;
32
33import com.jogamp.math.FloatUtil;
34import com.jogamp.math.VectorUtil;
35import com.jogamp.math.geom.AABBox;
36import com.jogamp.math.geom.plane.AffineTransform;
37import com.jogamp.math.geom.plane.Winding;
38import com.jogamp.graph.curve.OutlineShape;
39import com.jogamp.graph.curve.Region;
40
41
42
43/** Define a single continuous stroke by control vertices.
44 * The vertices define the shape of the region defined by this
45 * outline. The Outline can contain a list of off-curve and on-curve
46 * vertices which define curved regions.
47 *
48 * Note: An outline should be closed to be rendered as a region.
49 *
50 * @see OutlineShape
51 * @see Region
52 */
53public class Outline implements Comparable<Outline> {
54
55 private ArrayList<Vertex> vertices;
56 private boolean closed;
57 private final AABBox bbox;
58 private boolean dirtyBBox;
59 private Winding winding;
60 private boolean complexShape;
61 private int dirtyBits;
62
63 private static final int DIRTY_WINDING = 1 << 0;
64 private static final int DIRTY_COMPLEXSHAPE = 1 << 0;
65
66 /**Create an outline defined by control vertices.
67 * An outline can contain off Curve vertices which define curved
68 * regions in the outline.
69 */
70 public Outline() {
71 vertices = new ArrayList<Vertex>(3);
72 closed = false;
73 bbox = new AABBox();
74 dirtyBBox = false;
75 winding = Winding.CCW;
76 complexShape = false;
77 dirtyBits = 0;
78 }
79
80 /**
81 * Copy ctor
82 */
83 public Outline(final Outline src) {
84 final int count = src.vertices.size();
85 vertices = new ArrayList<Vertex>(count);
86 winding = Winding.CCW;
87 complexShape = false;
88 dirtyBits = DIRTY_WINDING | DIRTY_COMPLEXSHAPE;
89 if( 0 == ( src.dirtyBits & DIRTY_WINDING ) ) {
90 winding = src.winding;
91 dirtyBits &= ~DIRTY_WINDING;
92 }
93 if( 0 == ( src.dirtyBits & DIRTY_COMPLEXSHAPE ) ) {
94 complexShape = src.complexShape;
95 dirtyBits &= ~DIRTY_COMPLEXSHAPE;
96 }
97 for(int i=0; i<count; i++) {
98 vertices.add( src.vertices.get(i).copy() );
99 }
100 closed = src.closed;
101 bbox = new AABBox(src.bbox);
102 dirtyBBox = src.dirtyBBox;
103 }
104
105 /**
106 * Copy ctor w/ enforced Winding
107 * <p>
108 * If the enforced {@link Winding} doesn't match the source Outline, the vertices reversed copied into this new instance.
109 * </p>
110 * @param src the source Outline
111 * @param enforce {@link Winding} to be enforced on this copy
112 */
113 public Outline(final Outline src, final Winding enforce) {
114 final int count = src.vertices.size();
115 vertices = new ArrayList<Vertex>(count);
116 complexShape = false;
117 dirtyBits = DIRTY_COMPLEXSHAPE;
118 final Winding had_winding = src.getWinding();
119 winding = had_winding;
120 if( enforce != had_winding ) {
121 for(int i=count-1; i>=0; --i) {
122 vertices.add( src.vertices.get(i).copy() );
123 }
124 winding = enforce;
125 } else {
126 for(int i=0; i<count; ++i) {
127 vertices.add( src.vertices.get(i).copy() );
128 }
129 }
130 if( 0 == ( src.dirtyBits & DIRTY_COMPLEXSHAPE ) ) {
131 complexShape = src.complexShape;
132 dirtyBits &= ~DIRTY_COMPLEXSHAPE;
133 }
134 closed = src.closed;
135 bbox = new AABBox(src.bbox);
136 dirtyBBox = src.dirtyBBox;
137 }
138
139 /**
140 * Sets {@link Winding} to this outline
141 * <p>
142 * If the enforced {@link Winding} doesn't match this Outline, the vertices are reversed.
143 * </p>
144 * @param enforce to be enforced {@link Winding}
145 */
146 public final void setWinding(final Winding enforce) {
147 final Winding had_winding = getWinding();
148 if( enforce != had_winding ) {
149 final int count = vertices.size();
150 final ArrayList<Vertex> ccw = new ArrayList<Vertex>(count);
151 for(int i=count-1; i>=0; --i) {
152 ccw.add(vertices.get(i));
153 }
154 vertices = ccw;
155 winding = enforce;
156 dirtyBits &= ~DIRTY_WINDING;
157 }
158 }
159
160 /**
161 * Returns the cached or computed winding of this {@link Outline}s {@code polyline} using {@link VectorUtil#area(ArrayList)}.
162 * <p>
163 * The result is cached.
164 * </p>
165 * @return {@link Winding#CCW} or {@link Winding#CW}
166 */
167 public final Winding getWinding() {
168 if( 0 == ( dirtyBits & DIRTY_WINDING ) ) {
169 return winding;
170 }
171 final int count = getVertexCount();
172 if( 3 > count ) {
173 winding = Winding.CCW;
174 } else {
175 winding = VectorUtil.getWinding( getVertices() );
176 }
177 dirtyBits &= ~DIRTY_WINDING;
178 return winding;
179 }
180
181 /**
182 * Returns cached or computed result if whether this {@link Outline}s {@code polyline} is a complex shape.
183 * <p>
184 * A polyline with less than 3 elements is marked a simple shape for simplicity.
185 * </p>
186 * <p>
187 * The result is cached.
188 * </p>
189 */
190 public boolean isComplex() {
191 if( 0 == ( dirtyBits & DIRTY_COMPLEXSHAPE ) ) {
192 return complexShape;
193 }
194 complexShape = !VectorUtil.isConvex1( getVertices(), true );
195 // complexShape = VectorUtil.isSelfIntersecting1( getVertices() );
196 dirtyBits &= ~DIRTY_COMPLEXSHAPE;
197 return complexShape;
198 }
199
200 public final int getVertexCount() {
201 return vertices.size();
202 }
203
204 /**
205 * Appends a vertex to the outline loop/strip.
206 * @param vertex Vertex to be added
207 * @throws NullPointerException if the {@link Vertex} element is null
208 */
209 public final void addVertex(final Vertex vertex) throws NullPointerException {
210 addVertex(vertices.size(), vertex);
211 }
212
213 /**
214 * Insert the {@link Vertex} element at the given {@code position} to the outline loop/strip.
215 * @param position of the added Vertex
216 * @param vertex Vertex object to be added
217 * @throws NullPointerException if the {@link Vertex} element is null
218 * @throws IndexOutOfBoundsException if position is out of range (position < 0 || position > getVertexNumber())
219 */
220 public final void addVertex(final int position, final Vertex vertex) throws NullPointerException, IndexOutOfBoundsException {
221 if (null == vertex) {
222 throw new NullPointerException("vertex is null");
223 }
224 vertices.add(position, vertex);
225 if(!dirtyBBox) {
226 bbox.resize(vertex.getCoord());
227 }
228 dirtyBits |= DIRTY_WINDING | DIRTY_COMPLEXSHAPE;
229 }
230
231 /** Replaces the {@link Vertex} element at the given {@code position}.
232 * <p>Sets the bounding box dirty, hence a next call to {@link #getBounds()} will validate it.</p>
233 *
234 * @param position of the replaced Vertex
235 * @param vertex replacement Vertex object
236 * @throws NullPointerException if the {@link Outline} element is null
237 * @throws IndexOutOfBoundsException if position is out of range (position < 0 || position >= getVertexNumber())
238 */
239 public final void setVertex(final int position, final Vertex vertex) throws NullPointerException, IndexOutOfBoundsException {
240 if (null == vertex) {
241 throw new NullPointerException("vertex is null");
242 }
243 vertices.set(position, vertex);
244 dirtyBBox = true;
245 dirtyBits |= DIRTY_WINDING | DIRTY_COMPLEXSHAPE;
246 }
247
248 public final Vertex getVertex(final int index){
249 return vertices.get(index);
250 }
251
252 public int getVertexIndex(final Vertex vertex){
253 return vertices.indexOf(vertex);
254 }
255
256 /** Removes the {@link Vertex} element at the given {@code position}.
257 * <p>Sets the bounding box dirty, hence a next call to {@link #getBounds()} will validate it.</p>
258 *
259 * @param position of the to be removed Vertex
260 * @throws IndexOutOfBoundsException if position is out of range (position < 0 || position >= getVertexNumber())
261 */
262 public final Vertex removeVertex(final int position) throws IndexOutOfBoundsException {
263 dirtyBBox = true;
264 dirtyBits |= DIRTY_WINDING | DIRTY_COMPLEXSHAPE;
265 return vertices.remove(position);
266 }
267
268 public final boolean isEmpty(){
269 return (vertices.size() == 0);
270 }
271
272 public final Vertex getLastVertex(){
273 if(isEmpty()){
274 return null;
275 }
276 return vertices.get(vertices.size()-1);
277 }
278
279 public final ArrayList<Vertex> getVertices() {
280 return vertices;
281 }
282
283 /**
284 * Use the given outline loop/strip.
285 * <p>Validates the bounding box.</p>
286 *
287 * @param vertices the new outline loop/strip
288 */
289 public final void setVertices(final ArrayList<Vertex> vertices) {
290 this.vertices = vertices;
291 validateBoundingBox();
292 }
293
294 public final boolean isClosed() {
295 return closed;
296 }
297
298 /**
299 * Ensure this outline is closed.
300 * <p>
301 * Checks whether the last vertex equals to the first.
302 * If not equal, it either appends a copy of the first vertex
303 * or prepends a copy of the last vertex, depending on <code>closeTail</code>.
304 * </p>
305 * @param closeTail if true, a copy of the first vertex will be appended,
306 * otherwise a copy of the last vertex will be prepended.
307 * @return true if closing performed, otherwise false for NOP
308 */
309 public final boolean setClosed(final boolean closeTail) {
310 this.closed = true;
311 if( !isEmpty() ) {
312 final Vertex first = vertices.get(0);
313 final Vertex last = getLastVertex();
314 if( !first.getCoord().isEqual( last.getCoord() ) ) {
315 if( closeTail ) {
316 vertices.add(first.copy());
317 } else {
318 vertices.add(0, last.copy());
319 }
320 return true;
321 }
322 }
323 return false;
324 }
325
326 /**
327 * Return a transformed instance with all vertices are copied and transformed.
328 */
329 public final Outline transform(final AffineTransform t) {
330 final Outline newOutline = new Outline();
331 final int vsize = vertices.size();
332 for(int i=0; i<vsize; i++) {
333 final Vertex v = vertices.get(i);
334 newOutline.addVertex(t.transform(v, new Vertex()));
335 }
336 newOutline.closed = this.closed;
337 return newOutline;
338 }
339
340 private final void validateBoundingBox() {
341 dirtyBBox = false;
342 bbox.reset();
343 for (int i=0; i<vertices.size(); i++) {
344 bbox.resize(vertices.get(i).getCoord());
345 }
346 }
347
348 public final AABBox getBounds() {
349 if (dirtyBBox) {
350 validateBoundingBox();
351 }
352 return bbox;
353 }
354
355 /**
356 * Compare two outline's Bounding Box size.
357 * @see AABBox#getSize()
358 * @see java.lang.Comparable#compareTo(java.lang.Object)
359 */
360 @Override
361 public final int compareTo(final Outline other) {
362 final float thisSize = getBounds().getSize();
363 final float otherSize = other.getBounds().getSize();
364 if( FloatUtil.isEqual2(thisSize, otherSize) ) {
365 return 0;
366 } else if(thisSize < otherSize){
367 return -1;
368 } else {
369 return 1;
370 }
371 }
372
373 /**
374 * @param obj the Object to compare this Outline with
375 * @return true if {@code obj} is an Outline, not null, equals bounds and equal vertices in the same order
376 */
377 @Override
378 public boolean equals(final Object obj) {
379 if( obj == this) {
380 return true;
381 }
382 if( null == obj || !(obj instanceof Outline) ) {
383 return false;
384 }
385 final Outline o = (Outline) obj;
386 if(getVertexCount() != o.getVertexCount()) {
387 return false;
388 }
389 if( !getBounds().equals( o.getBounds() ) ) {
390 return false;
391 }
392 for (int i=getVertexCount()-1; i>=0; i--) {
393 if( ! getVertex(i).equals( o.getVertex(i) ) ) {
394 return false;
395 }
396 }
397 return true;
398 }
399 @Override
400 public final int hashCode() {
401 throw new InternalError("hashCode not designed");
402 }
403 @Override
404 public String toString() {
405 // Avoid calling this.hashCode() !
406 return getClass().getName() + "@" + Integer.toHexString(super.hashCode());
407 }
408
409 public void print(final PrintStream out) {
410 final int vc = getVertexCount();
411 out.printf("Outline: %d, %s%n", vc, getWinding());
412 for(int vi=0; vi < vc; vi++) {
413 final Vertex v = getVertex(vi);
414 out.printf("- OL[%d]: %s%n", vi, v);
415 }
416 }
417}
Define a single continuous stroke by control vertices.
Definition: Outline.java:53
final void addVertex(final int position, final Vertex vertex)
Insert the Vertex element at the given position to the outline loop/strip.
Definition: Outline.java:220
Outline(final Outline src)
Copy ctor.
Definition: Outline.java:83
final Winding getWinding()
Returns the cached or computed winding of this Outlines polyline using VectorUtil#area(ArrayList).
Definition: Outline.java:167
final void setVertex(final int position, final Vertex vertex)
Replaces the Vertex element at the given position.
Definition: Outline.java:239
final void setVertices(final ArrayList< Vertex > vertices)
Use the given outline loop/strip.
Definition: Outline.java:289
final boolean setClosed(final boolean closeTail)
Ensure this outline is closed.
Definition: Outline.java:309
final Vertex getLastVertex()
Definition: Outline.java:272
boolean equals(final Object obj)
Definition: Outline.java:378
final int compareTo(final Outline other)
Compare two outline's Bounding Box size.
Definition: Outline.java:361
final boolean isClosed()
Definition: Outline.java:294
final Outline transform(final AffineTransform t)
Return a transformed instance with all vertices are copied and transformed.
Definition: Outline.java:329
void print(final PrintStream out)
Definition: Outline.java:409
final void addVertex(final Vertex vertex)
Appends a vertex to the outline loop/strip.
Definition: Outline.java:209
final ArrayList< Vertex > getVertices()
Definition: Outline.java:279
final Vertex getVertex(final int index)
Definition: Outline.java:248
Outline(final Outline src, final Winding enforce)
Copy ctor w/ enforced Winding.
Definition: Outline.java:113
final void setWinding(final Winding enforce)
Sets Winding to this outline.
Definition: Outline.java:146
boolean isComplex()
Returns cached or computed result if whether this Outlines polyline is a complex shape.
Definition: Outline.java:190
final Vertex removeVertex(final int position)
Removes the Vertex element at the given position.
Definition: Outline.java:262
int getVertexIndex(final Vertex vertex)
Definition: Outline.java:252
Outline()
Create an outline defined by control vertices.
Definition: Outline.java:70
A Vertex exposing Vec3f vertex- and texture-coordinates.
Definition: Vertex.java:37
boolean equals(final Object obj)
Definition: Vertex.java:167
Basic Float math utility functions.
Definition: FloatUtil.java:83
static boolean isEqual2(final float a, final float b)
Returns true if both values are equal, i.e.
boolean isEqual(final Vec3f o, final float epsilon)
Equals check using a given FloatUtil#EPSILON value and FloatUtil#isEqual(float, float,...
Definition: Vec3f.java:383
static Winding getWinding(final Vert2fImmutable a, final Vert2fImmutable b, final Vert2fImmutable c)
Compute the winding of the 3 given points.
static boolean isConvex1(final List< Vertex > polyline, final boolean shortIsConvex)
Returns whether the given on-curve polyline points denotes a convex shape with O(n) complexity.
Axis Aligned Bounding Box.
Definition: AABBox.java:54
final boolean equals(final Object obj)
Definition: AABBox.java:912
final AABBox reset()
Resets this box to the inverse low/high, allowing the next resize(float, float, float) command to hit...
Definition: AABBox.java:123
final float getSize()
Get the size of this AABBox where the size is represented by the length of the vector between low and...
Definition: AABBox.java:732
final AABBox resize(final AABBox newBox)
Resize the AABBox to encapsulate another AABox.
Definition: AABBox.java:274
final AABBox transform(final AABBox src, final AABBox dst)
Winding direction, either clockwise (CW) or counter-clockwise (CCW).
Definition: Winding.java:6
CCW
Counter-Clockwise (CCW) positive winding direction.
Definition: Winding.java:10