/**
* Copyright 2010-2023 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 {
private ArrayList vertices;
private boolean closed;
private final AABBox bbox;
private boolean dirtyBBox;
private Winding winding;
private boolean dirtyWinding;
/**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(3);
closed = false;
bbox = new AABBox();
dirtyBBox = false;
winding = Winding.CCW;
dirtyWinding = false;
}
/**
* Copy ctor
*/
public Outline(final Outline src) {
final int count = src.vertices.size();
vertices = new ArrayList(count);
winding = src.getWinding();
dirtyWinding = false;
for(int i=0; i
* If the enforced {@link Winding} doesn't match the source Outline, the vertices reversed copied into this new instance.
*
* @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(count);
final Winding had_winding = src.getWinding();;
winding = had_winding;
dirtyWinding = false;
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
* If the enforced {@link Winding} doesn't match this Outline, the vertices are reversed.
*
* @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 ccw = new ArrayList(count);
for(int i=count-1; i>=0; --i) {
ccw.add(vertices.get(i));
}
vertices = ccw;
winding = enforce;
}
}
/**
* Compute the winding of the {@link #getLastOutline()} using the {@link #area(ArrayList)} function over all of its vertices.
* @return {@link Winding#CCW} or {@link Winding#CW}
*/
public final Winding getWinding() {
if( !dirtyWinding ) {
return winding;
}
final int count = getVertexCount();
if( 3 > count ) {
winding = Winding.CCW;
} else {
winding = VectorUtil.getWinding( getVertices() );
}
dirtyWinding = false;
return winding;
}
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());
}
dirtyWinding = true;
}
/** Replaces the {@link Vertex} element at the given {@code position}.
* Sets the bounding box dirty, hence a next call to {@link #getBounds()} will validate it.
*
* @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;
dirtyWinding = true;
}
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}.
* Sets the bounding box dirty, hence a next call to {@link #getBounds()} will validate it.
*
* @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;
dirtyWinding = true;
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 getVertices() {
return vertices;
}
/**
* Use the given outline loop/strip.
* Validates the bounding box.
*
* @param vertices the new outline loop/strip
*/
public final void setVertices(final ArrayList vertices) {
this.vertices = vertices;
validateBoundingBox();
}
public final boolean isClosed() {
return closed;
}
/**
* Ensure this outline is closed.
*
* 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 closeTail
.
*
* @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=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);
}
}
}