/** * Copyright 2010 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.curve; import java.util.ArrayList; import java.util.Collections; import com.jogamp.graph.curve.tess.Triangulation; import com.jogamp.graph.curve.tess.Triangulator; import com.jogamp.graph.geom.AABBox; import com.jogamp.graph.geom.Outline; import com.jogamp.graph.geom.Triangle; import com.jogamp.graph.geom.Vertex; import com.jogamp.graph.math.VectorUtil; /** A Generic shape objects which is defined by a list of Outlines. * This Shape can be transformed to Triangulations. * The list of triangles generated are render-able by a Region object. * The triangulation produced by this Shape will define the * closed region defined by the outlines. * * One or more OutlineShape Object can be associated to a region * this is left as a high-level representation of the Objects. For * optimizations, flexibility requirements for future features. * *

* Example to creating an Outline Shape: *
      addVertex(...)
      addVertex(...)
      addVertex(...)
      addEmptyOutline()
      addVertex(...)
      addVertex(...)
      addVertex(...)
 * 
* * The above will create two outlines each with three vertices. By adding these two outlines to * the OutlineShape, we are stating that the combination of the two outlines represent the shape. *
* * To specify that the shape is curved at a region, the on-curve flag should be set to false * for the vertex that is in the middle of the curved region (if the curved region is defined by 3 * vertices (quadratic curve). *
* In case the curved region is defined by 4 or more vertices the middle vertices should both have * the on-curve flag set to false. * *
Example:
*
      addVertex(0,0, true);
      addVertex(0,1, false);
      addVertex(1,1, false);
      addVertex(1,0, true);
 * 
* * The above snippet defines a cubic nurbs curve where (0,1 and 1,1) * do not belong to the final rendered shape. * * Implementation Notes:
* * * @see Outline * @see Region */ public class OutlineShape implements Comparable { /** * Outline's vertices have undefined state until transformed. */ public enum VerticesState { UNDEFINED(0), QUADRATIC_NURBS(1); public final int state; VerticesState(int state){ this.state = state; } } public static final int DIRTY_BOUNDS = 1 << 0; private final Vertex.Factory vertexFactory; private VerticesState outlineState; /** The list of {@link Outline}s that are part of this * outline shape. */ private ArrayList outlines; private AABBox bbox; /** dirty bits DIRTY_BOUNDS */ private int dirtyBits; /** Create a new Outline based Shape */ public OutlineShape(Vertex.Factory factory) { this.vertexFactory = factory; this.outlines = new ArrayList(3); this.outlines.add(new Outline()); this.outlineState = VerticesState.UNDEFINED; this.bbox = new AABBox(); this.dirtyBits = 0; } /** Clears all data and reset all states as if this instance was newly created */ public void clear() { outlines.clear(); outlines.add(new Outline()); outlineState = VerticesState.UNDEFINED; bbox.reset(); dirtyBits = 0; } /** Returns the associated vertex factory of this outline shape * @return Vertex.Factory object */ public final Vertex.Factory vertexFactory() { return vertexFactory; } public int getOutlineNumber() { return outlines.size(); } /** Add a new empty {@link Outline} * to the end of this shape's outline list. *

If the {@link #getLastOutline()} is empty already, no new one will be added.

* * After a call to this function all new vertices added * will belong to the new outline */ public void addEmptyOutline() { if( !getLastOutline().isEmpty() ) { outlines.add(new Outline()); } } /** Appends the {@link Outline} element to the end, * ensuring a clean tail. * *

A clean tail is ensured, no double empty Outlines are produced * and a pre-existing empty outline will be replaced with the given one.

* * @param outline Outline object to be added * @throws NullPointerException if the {@link Outline} element is null */ public void addOutline(Outline outline) throws NullPointerException { addOutline(outlines.size(), outline); } /** Insert the {@link Outline} element at the given {@code position}. * *

If the {@code position} indicates the end of this list, * a clean tail is ensured, no double empty Outlines are produced * and a pre-existing empty outline will be replaced with the given one.

* * @param position of the added Outline * @param outline Outline object to be added * @throws NullPointerException if the {@link Outline} element is null * @throws IndexOutOfBoundsException if position is out of range (position < 0 || position > getOutlineNumber()) */ public void addOutline(int position, Outline outline) throws NullPointerException, IndexOutOfBoundsException { if (null == outline) { throw new NullPointerException("outline is null"); } if( outlines.size() == position ) { final Outline lastOutline = getLastOutline(); if( outline.isEmpty() && lastOutline.isEmpty() ) { return; } if( lastOutline.isEmpty() ) { outlines.set(position-1, outline); if( 0 == ( dirtyBits & DIRTY_BOUNDS ) ) { bbox.resize(outline.getBounds()); } return; } } outlines.add(position, outline); if( 0 == ( dirtyBits & DIRTY_BOUNDS ) ) { bbox.resize(outline.getBounds()); } } /** Insert the {@link OutlineShape} elements of type {@link Outline}, .. at the end of this shape, * using {@link #addOutline(Outline)} for each element. *

Closes the current last outline via {@link #closeLastOutline()} before adding the new ones.

* @param outlineShape OutlineShape elements to be added. * @throws NullPointerException if the {@link OutlineShape} is null * @throws IndexOutOfBoundsException if position is out of range (position < 0 || position > getOutlineNumber()) */ public void addOutlineShape(OutlineShape outlineShape) throws NullPointerException { if (null == outlineShape) { throw new NullPointerException("OutlineShape is null"); } closeLastOutline(); for(int i=0; iSets the bounding box dirty, hence a next call to {@link #getBounds()} will validate it.

* * @param position of the replaced Outline * @param outline replacement Outline object * @throws NullPointerException if the {@link Outline} element is null * @throws IndexOutOfBoundsException if position is out of range (position < 0 || position >= getOutlineNumber()) */ public void setOutline(int position, Outline outline) throws NullPointerException, IndexOutOfBoundsException { if (null == outline) { throw new NullPointerException("outline is null"); } outlines.set(position, outline); dirtyBits |= DIRTY_BOUNDS; } /** Removes the {@link Outline} 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 Outline * @throws IndexOutOfBoundsException if position is out of range (position < 0 || position >= getOutlineNumber()) */ public final Outline removeOutline(int position) throws IndexOutOfBoundsException { dirtyBits |= DIRTY_BOUNDS; return outlines.remove(position); } /** Get the last added outline to the list * of outlines that define the shape * @return the last outline */ public final Outline getLastOutline() { return outlines.get(outlines.size()-1); } /** @return the {@code Outline} at {@code position} * @throws IndexOutOfBoundsException if position is out of range (position < 0 || position >= getOutlineNumber()) */ public Outline getOutline(int position) throws IndexOutOfBoundsException { return outlines.get(position); } /** Adds a vertex to the last open outline in the * shape. * @param v the vertex to be added to the OutlineShape */ public final void addVertex(Vertex v) { final Outline lo = getLastOutline(); lo.addVertex(v); if( 0 == ( dirtyBits & DIRTY_BOUNDS ) ) { bbox.resize(lo.getBounds()); } } /** Adds a vertex to the last open outline in the shape. * at {@code position} * @param position indx at which the vertex will be added * @param v the vertex to be added to the OutlineShape */ public final void addVertex(int position, Vertex v) { final Outline lo = getLastOutline(); lo.addVertex(position, v); if( 0 == ( dirtyBits & DIRTY_BOUNDS ) ) { bbox.resize(lo.getBounds()); } } /** Add a 2D {@link Vertex} to the last outline by defining the coordniate attribute * of the vertex. The 2D vertex will be represented as Z=0. * * @param x the x coordinate * @param y the y coordniate * @param onCurve flag if this vertex is on the final curve or defines a curved region * of the shape around this vertex. */ public final void addVertex(float x, float y, boolean onCurve) { addVertex(vertexFactory.create(x, y, 0f, onCurve)); } /** Add a 3D {@link Vertex} to the last outline by defining the coordniate attribute * of the vertex. * @param x the x coordinate * @param y the y coordinate * @param z the z coordinate * @param onCurve flag if this vertex is on the final curve or defines a curved region * of the shape around this vertex. */ public final void addVertex(float x, float y, float z, boolean onCurve) { addVertex(vertexFactory.create(x, y, z, onCurve)); } /** Add a vertex to the last outline by passing a float array and specifying the * offset and length in which. The attributes of the vertex are located. * The attributes should be continuous (stride = 0). * Attributes which value are not set (when length less than 3) * are set implicitly to zero. * @param coordsBuffer the coordinate array where the vertex attributes are to be picked from * @param offset the offset in the buffer to the x coordinate * @param length the number of attributes to pick from the buffer (maximum 3) * @param onCurve flag if this vertex is on the final curve or defines a curved region * of the shape around this vertex. */ public final void addVertex(float[] coordsBuffer, int offset, int length, boolean onCurve) { addVertex(vertexFactory.create(coordsBuffer, offset, length, onCurve)); } /** Closes the last outline in the shape. *

If last vertex is not equal to first vertex. * A new temp vertex is added at the end which * is equal to the first.

*/ public void closeLastOutline() { getLastOutline().setClosed(true); } /** * @return the outline's vertices state, {@link OutlineShape.VerticesState} */ public final VerticesState getOutlineState() { return outlineState; } /** Ensure the outlines represent * the specified destinationType. * and removes all overlaps in boundary triangles * @param destinationType the target outline's vertices state. Currently only * {@link OutlineShape.VerticesState#QUADRATIC_NURBS} are supported. */ public void transformOutlines(VerticesState destinationType) { if(outlineState != destinationType){ if(destinationType == VerticesState.QUADRATIC_NURBS){ transformOutlines2Quadratic(); checkOverlaps(); } else { throw new IllegalStateException("destinationType "+destinationType.name()+" not supported (currently "+outlineState.name()+")"); } } } private void subdivideTriangle(final Outline outline, Vertex a, Vertex b, Vertex c, int index){ float[] v1 = VectorUtil.mid(a.getCoord(), b.getCoord()); float[] v3 = VectorUtil.mid(b.getCoord(), c.getCoord()); float[] v2 = VectorUtil.mid(v1, v3); //drop off-curve vertex to image on the curve b.setCoord(v2, 0, 3); b.setOnCurve(true); outline.addVertex(index, vertexFactory.create(v1, 0, 3, false)); outline.addVertex(index+2, vertexFactory.create(v3, 0, 3, false)); } /** Check overlaps between curved triangles * first check if any vertex in triangle a is in triangle b * second check if edges of triangle a intersect segments of triangle b * if any of the two tests is true we divide current triangle * and add the other to the list of overlaps * * Loop until overlap array is empty. (check only in first pass) */ private void checkOverlaps() { ArrayList overlaps = new ArrayList(3); int count = getOutlineNumber(); boolean firstpass = true; do { for (int cc = 0; cc < count; cc++) { final Outline outline = getOutline(cc); int vertexCount = outline.getVertexCount(); for(int i=0; i < outline.getVertexCount(); i++) { final Vertex currentVertex = outline.getVertex(i); if ( !currentVertex.isOnCurve()) { final Vertex nextV = outline.getVertex((i+1)%vertexCount); final Vertex prevV = outline.getVertex((i+vertexCount-1)%vertexCount); Vertex overlap =null; //check for overlap even if already set for subdivision //ensuring both trianglur overlaps get divided //for pref. only check in first pass //second pass to clear the overlaps arrray(reduces precision errors) if(firstpass) { overlap = checkTriOverlaps(prevV, currentVertex, nextV); } if(overlaps.contains(currentVertex) || overlap != null) { overlaps.remove(currentVertex); subdivideTriangle(outline, prevV, currentVertex, nextV, i); i+=3; vertexCount+=2; if(overlap != null && !overlap.isOnCurve()) { if(!overlaps.contains(overlap)) overlaps.add(overlap); } } } } } firstpass = false; }while(!overlaps.isEmpty()); } private Vertex checkTriOverlaps(Vertex a, Vertex b, Vertex c) { int count = getOutlineNumber(); for (int cc = 0; cc < count; cc++) { final Outline outline = getOutline(cc); int vertexCount = outline.getVertexCount(); for(int i=0; i < vertexCount; i++) { final Vertex current = outline.getVertex(i); if(current.isOnCurve() || current == a || current == b || current == c) { continue; } final Vertex nextV = outline.getVertex((i+1)%vertexCount); final Vertex prevV = outline.getVertex((i+vertexCount-1)%vertexCount); //skip neighboring triangles if(prevV == c || nextV == a) { continue; } if(VectorUtil.vertexInTriangle(a.getCoord(), b.getCoord(), c.getCoord(), current.getCoord()) || VectorUtil.vertexInTriangle(a.getCoord(), b.getCoord(), c.getCoord(), nextV.getCoord()) || VectorUtil.vertexInTriangle(a.getCoord(), b.getCoord(), c.getCoord(), prevV.getCoord())) { return current; } if(VectorUtil.tri2SegIntersection(a, b, c, prevV, current) || VectorUtil.tri2SegIntersection(a, b, c, current, nextV) || VectorUtil.tri2SegIntersection(a, b, c, prevV, nextV)) { return current; } } } return null; } private void transformOutlines2Quadratic() { int count = getOutlineNumber(); for (int cc = 0; cc < count; cc++) { final Outline outline = getOutline(cc); int vertexCount = outline.getVertexCount(); for(int i=0; i < vertexCount; i++) { final Vertex currentVertex = outline.getVertex(i); final Vertex nextVertex = outline.getVertex((i+1)%vertexCount); if ( !currentVertex.isOnCurve() && !nextVertex.isOnCurve() ) { final float[] newCoords = VectorUtil.mid(currentVertex.getCoord(), nextVertex.getCoord()); final Vertex v = vertexFactory.create(newCoords, 0, 3, true); i++; vertexCount++; outline.addVertex(i, v); } } if(vertexCount <= 0) { outlines.remove(outline); cc--; count--; continue; } if( vertexCount > 0 ) { if(VectorUtil.checkEquality(outline.getVertex(0).getCoord(), outline.getLastVertex().getCoord())) { outline.removeVertex(vertexCount-1); } } } outlineState = VerticesState.QUADRATIC_NURBS; } private void generateVertexIds() { int maxVertexId = 0; for(int i=0; i vertices = outlines.get(i).getVertices(); for(int pos=0; pos getVertices() { ArrayList vertices = new ArrayList(); for(int i=0; i triangulate() { if(outlines.size() == 0){ return null; } sortOutlines(); generateVertexIds(); Triangulator triangulator2d = Triangulation.create(); for(int index = 0; index triangles = triangulator2d.generate(); triangulator2d.reset(); return triangles; } /** Sort the outlines from large * to small depending on the AABox */ private void sortOutlines() { Collections.sort(outlines); Collections.reverse(outlines); } /** Compare two outline shapes with Bounding Box area * as criteria. * @see java.lang.Comparable#compareTo(java.lang.Object) */ public final int compareTo(OutlineShape outline) { float size = getBounds().getSize(); float newSize = outline.getBounds().getSize(); if(size < newSize){ return -1; } else if(size > newSize){ return 1; } return 0; } private final void validateBoundingBox() { dirtyBits &= ~DIRTY_BOUNDS; bbox.reset(); for (int i=0; i=0; i--) { if( ! getOutline(i).equals( o.getOutline(i) ) ) { return false; } } return true; } /** * @return deep clone of this OutlineShape w/o Region */ public OutlineShape clone() { OutlineShape o; try { o = (OutlineShape) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } o.bbox = bbox.clone(); o.outlines = new ArrayList(outlines.size()); for(int i=0; i