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