/** * 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 = Winding.CCW; dirtyWinding = true; 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 VectorUtil#area2d(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); } } }