999 lines
30 KiB
Java
999 lines
30 KiB
Java
/**
|
|
Copyright 2004-2008 Ricard Marxer <email@ricardmarxer.com>
|
|
|
|
This file is part of Geomerative.
|
|
|
|
Geomerative is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Geomerative is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Geomerative. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package geomerative ;
|
|
import processing.core.*;
|
|
|
|
/**
|
|
* RPath is a reduced interface for creating, holding and drawing contours. Paths are ordered lists of commands (RCommand) which define the outlines of shapes. Paths can be self-intersecting.
|
|
* @eexample RPath
|
|
* @usage Geometry
|
|
* @related RCommand
|
|
* @related RPolygon
|
|
* @extended
|
|
*
|
|
*/
|
|
public class RPath extends RGeomElem
|
|
{
|
|
/**
|
|
* @invisible
|
|
*/
|
|
public int type = RGeomElem.SUBSHAPE;
|
|
|
|
/**
|
|
* Array of RCommand objects holding the commands of the path.
|
|
* @eexample commands
|
|
* @related RCommand
|
|
* @related countCommands ( )
|
|
*/
|
|
public RCommand[] commands;
|
|
|
|
/**
|
|
* Last point from where to add the next command. Initialized to (0, 0).
|
|
* @eexample lastPoint
|
|
* @related RPoint
|
|
* @invisible
|
|
*/
|
|
public RPoint lastPoint;
|
|
|
|
boolean closed = false;
|
|
|
|
/**
|
|
* Create a new empty path.
|
|
* @eexample RPath
|
|
*/
|
|
public RPath(){
|
|
this.lastPoint = new RPoint();
|
|
}
|
|
|
|
/**
|
|
* Create a new path, given an array of points.
|
|
* @eexample RPath
|
|
* @param points the points of the new path
|
|
*/
|
|
public RPath(RPoint[] points){
|
|
if(points == null) return;
|
|
this.lastPoint = points[0];
|
|
|
|
for(int i = 1; i < points.length; i++){
|
|
this.addLineTo(points[i]);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Create a new path, given the coordinates of the first point.
|
|
* @eexample RPath
|
|
* @param x x coordinate of the first point of the new path
|
|
* @param y y coordinate of the first point of the new path
|
|
*/
|
|
public RPath(float x, float y){
|
|
this.lastPoint = new RPoint(x,y);
|
|
}
|
|
|
|
/**
|
|
* Create a new path, given the first point.
|
|
* @eexample RPath
|
|
* @param p first point of the new path
|
|
*/
|
|
public RPath(RPoint p){
|
|
this.lastPoint = p;
|
|
}
|
|
|
|
/**
|
|
* Copy a path.
|
|
* @eexample RPath
|
|
* @param s path to be copied
|
|
*/
|
|
public RPath(RPath s){
|
|
int numCommands = s.countCommands();
|
|
if(numCommands!=0){
|
|
lastPoint = new RPoint(s.commands[0].startPoint);
|
|
for(int i=0;i<numCommands;i++){
|
|
this.append(new RCommand(s.commands[i], lastPoint));
|
|
lastPoint = commands[i].endPoint;
|
|
}
|
|
}
|
|
|
|
closed = s.closed;
|
|
setStyle(s);
|
|
}
|
|
|
|
public RPath(RCommand c){
|
|
this();
|
|
this.addCommand(c);
|
|
}
|
|
|
|
/**
|
|
* Use this method to count the number of commands in the contour.
|
|
* @eexample countCommands
|
|
* @return int, the number commands in the contour
|
|
*/
|
|
public int countCommands(){
|
|
if(this.commands==null){
|
|
return 0;
|
|
}
|
|
|
|
return this.commands.length;
|
|
}
|
|
|
|
/**
|
|
* Use this to return the start, control and end points of the path. It returns the points in the way of an array of RPoint.
|
|
* @eexample getHandles
|
|
* @return RPoint[], the start, control and end points returned in an array.
|
|
* */
|
|
public RPoint[] getHandles(){
|
|
int numCommands = countCommands();
|
|
|
|
RPoint[] result = null;
|
|
RPoint[] newresult = null;
|
|
for( int i = 0; i < numCommands ; i++ ){
|
|
RPoint[] newPoints = commands[i].getHandles();
|
|
if(newPoints != null){
|
|
if(result == null){
|
|
result = new RPoint[newPoints.length];
|
|
System.arraycopy(newPoints,0,result,0,newPoints.length);
|
|
}else{
|
|
int overlap = 0;
|
|
if(newPoints[0] == result[result.length-1]){
|
|
overlap = 1;
|
|
}
|
|
newresult = new RPoint[result.length + newPoints.length - overlap];
|
|
System.arraycopy(result,0,newresult,0,result.length);
|
|
System.arraycopy(newPoints,overlap,newresult,result.length,newPoints.length - overlap);
|
|
result = newresult;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Use this to return the points on the curve. It returns the points in the way of an array of RPoint.
|
|
* @eexample getPoints
|
|
* @return RPoint[], the vertices returned in an array.
|
|
* */
|
|
public RPoint[] getPoints(){
|
|
int numCommands = countCommands();
|
|
if(numCommands == 0){
|
|
return null;
|
|
}
|
|
|
|
// Add the curve points of each command
|
|
|
|
// First set the accumulated offset to the value of the inital offset
|
|
RCommand.segmentAccOffset = RCommand.segmentOffset;
|
|
RPoint[] result=null;
|
|
RPoint[] newresult=null;
|
|
for(int i=0;i<numCommands;i++){
|
|
RPoint[] newPoints = commands[i].getPoints(false);
|
|
if(newPoints!=null){
|
|
if(result==null){
|
|
result = new RPoint[newPoints.length];
|
|
System.arraycopy(newPoints,0,result,0,newPoints.length);
|
|
}else{
|
|
// Check for overlapping
|
|
// Overlapping happens when the last point of the last command
|
|
// is the same as the first point of the current command
|
|
RPoint lastp = result[result.length-1];
|
|
RPoint firstp = newPoints[0];
|
|
int overlap = 0;
|
|
if((lastp.x == firstp.x) && (lastp.y == firstp.y)) {
|
|
overlap = 1;
|
|
}
|
|
newresult = new RPoint[result.length + newPoints.length - overlap];
|
|
System.arraycopy(result,0,newresult,0,result.length);
|
|
System.arraycopy(newPoints,overlap,newresult,result.length,newPoints.length - overlap);
|
|
result = newresult;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Always add last point
|
|
newresult = new RPoint[result.length + 1];
|
|
System.arraycopy(result,0,newresult,0,result.length);
|
|
newresult[newresult.length - 1] = new RPoint(commands[numCommands-1].endPoint);
|
|
|
|
return newresult;
|
|
}
|
|
|
|
/**
|
|
* Use this to return the points of each path of the path. It returns the points in the way of an array of array of RPoint.
|
|
* @eexample RGroup_getPoints
|
|
* @return RPoint[], the points returned in an array.
|
|
* */
|
|
public RPoint[][] getPointsInPaths(){
|
|
RPoint[][] result = {this.getPoints()};
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Use this to return the handles of each path of the path. It returns the handles in the way of an array of array of RPoint.
|
|
* @eexample RGroup_getHandles
|
|
* @return RPoint[], the handles returned in an array.
|
|
* */
|
|
public RPoint[][] getHandlesInPaths(){
|
|
RPoint[][] result = {this.getHandles()};
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Use this to return the tangents of each path of the path. It returns the tangents in the way of an array of array of RPoint.
|
|
* @eexample RGroup_getTangents
|
|
* @return RPoint[], the tangents returned in an array.
|
|
* */
|
|
public RPoint[][] getTangentsInPaths(){
|
|
RPoint[][] result = {this.getTangents()};
|
|
return result;
|
|
}
|
|
|
|
protected void calculateCurveLengths(){
|
|
lenCurves = new float[countCommands()];
|
|
lenCurve = 0F;
|
|
for(int i=0;i<countCommands();i++){
|
|
lenCurves[i] = commands[i].getCurveLength();
|
|
lenCurve += lenCurves[i];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Use this to return the tangents on the curve. It returns the vectors in the way of an array of RPoint.
|
|
* @eexample getTangents
|
|
* @return RPoint[], the tangent vectors returned in an array.
|
|
* */
|
|
public RPoint[] getTangents(){
|
|
int numCommands = countCommands();
|
|
if(numCommands == 0){
|
|
return null;
|
|
}
|
|
|
|
RPoint[] result=null;
|
|
RPoint[] newresult=null;
|
|
for(int i=0;i<numCommands;i++){
|
|
RPoint[] newTangents = commands[i].getTangents();
|
|
if(newTangents!=null){
|
|
if(newTangents.length!=1){
|
|
int overlap = 1;
|
|
if(result==null){
|
|
result = new RPoint[newTangents.length];
|
|
System.arraycopy(newTangents,0,result,0,newTangents.length);
|
|
}else{
|
|
newresult = new RPoint[result.length + newTangents.length - overlap];
|
|
System.arraycopy(result,0,newresult,0,result.length);
|
|
System.arraycopy(newTangents,overlap,newresult,result.length,newTangents.length - overlap);
|
|
result = newresult;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Use this to return the intersection points between this path and a command. Returns null if no intersection exists.
|
|
* @return RPoint[], the intersection points returned in an array.
|
|
* */
|
|
public RPoint[] intersectionPoints(RCommand other){
|
|
int numCommands = countCommands();
|
|
if(numCommands == 0){
|
|
return null;
|
|
}
|
|
|
|
RPoint[] result=null;
|
|
RPoint[] newresult=null;
|
|
for(int i=0;i<numCommands;i++){
|
|
RPoint[] newPoints = commands[i].intersectionPoints(other);
|
|
if(newPoints!=null) {
|
|
if(result==null){
|
|
result = new RPoint[newPoints.length];
|
|
System.arraycopy(newPoints,0,result,0,newPoints.length);
|
|
}else{
|
|
newresult = new RPoint[result.length + newPoints.length];
|
|
System.arraycopy(result,0,newresult,0,result.length);
|
|
System.arraycopy(newPoints,0,newresult,result.length,newPoints.length);
|
|
result = newresult;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Use this to return the intersection points between two paths. Returns null if no intersection exists.
|
|
* @return RPoint[], the intersection points returned in an array.
|
|
* */
|
|
public RPoint[] intersectionPoints(RPath other){
|
|
int numCommands = countCommands();
|
|
int numOtherCommands = other.countCommands();
|
|
|
|
if(numCommands == 0){
|
|
return null;
|
|
}
|
|
|
|
RPoint[] result=null;
|
|
RPoint[] newresult=null;
|
|
|
|
for(int j=0;j<numOtherCommands;j++){
|
|
for(int i=0;i<numCommands;i++){
|
|
RPoint[] newPoints = commands[i].intersectionPoints(other.commands[j]);
|
|
if(newPoints!=null){
|
|
if(result==null){
|
|
result = new RPoint[newPoints.length];
|
|
System.arraycopy(newPoints,0,result,0,newPoints.length);
|
|
}else{
|
|
newresult = new RPoint[result.length + newPoints.length];
|
|
System.arraycopy(result,0,newresult,0,result.length);
|
|
System.arraycopy(newPoints,0,newresult,result.length,newPoints.length);
|
|
result = newresult;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* Use this to find the closest or intersection points between this path and a command.
|
|
* @return RPoint[], the intersection points returned in an array.
|
|
* */
|
|
public RClosest closestPoints(RCommand other){
|
|
int numCommands = countCommands();
|
|
if(numCommands == 0){
|
|
return null;
|
|
}
|
|
|
|
// TODO: get here the max value of an integer
|
|
float minDist = 100000;
|
|
|
|
RClosest result = new RClosest();
|
|
|
|
for(int i=0;i<numCommands;i++){
|
|
RClosest currResult = commands[i].closestPoints(other);
|
|
|
|
result.update(currResult);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Use this to return the intersection points between two paths. Returns null if no intersection exists.
|
|
* @return RPoint[], the intersection points returned in an array.
|
|
* */
|
|
public RClosest closestPoints(RPath other){
|
|
int numCommands = countCommands();
|
|
int numOtherCommands = other.countCommands();
|
|
|
|
if(numCommands == 0){
|
|
return null;
|
|
}
|
|
|
|
// TODO: get here the max value of an integer
|
|
float minDist = 100000;
|
|
|
|
RClosest result = new RClosest();
|
|
|
|
for(int j=0;j<numOtherCommands;j++){
|
|
for(int i=0;i<numCommands;i++){
|
|
RClosest currResult = commands[i].closestPoints(other.commands[j]);
|
|
result.update(currResult);
|
|
}
|
|
}
|
|
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a specific point on the curve. It returns the RPoint for a given advancement parameter t on the curve.
|
|
* @eexample getPoint
|
|
* @param t the parameter of advancement on the curve. t must have values between 0 and 1.
|
|
* @return RPoint, the vertice returned.
|
|
* */
|
|
public RPoint getPoint(float t){
|
|
int numCommands = countCommands();
|
|
if(numCommands == 0){
|
|
return new RPoint();
|
|
}
|
|
|
|
if(t==0.0F){ return commands[0].getPoint(0F); }
|
|
if(t==1.0F){ return commands[numCommands-1].getPoint(1F); }
|
|
|
|
float[] indAndAdv = indAndAdvAt(t);
|
|
int indOfElement = (int)(indAndAdv[0]);
|
|
float advOfElement = indAndAdv[1];
|
|
|
|
return commands[indOfElement].getPoint(advOfElement);
|
|
}
|
|
|
|
/**
|
|
* Use this to return a specific tangent on the curve. It returns the RPoint tangent for a given advancement parameter t on the curve.
|
|
* @eexample getPoint
|
|
* @param t float, the parameter of advancement on the curve. t must have values between 0 and 1.
|
|
* @return RPoint, the vertice returned.
|
|
* */
|
|
public RPoint getTangent(float t){
|
|
int numCommands = countCommands();
|
|
if(numCommands == 0){
|
|
return new RPoint();
|
|
}
|
|
|
|
if(t==0.0F){ return commands[0].getTangent(0F); }
|
|
if(t==1.0F){ return commands[numCommands-1].getTangent(1F); }
|
|
|
|
float[] indAndAdv = indAndAdvAt(t);
|
|
int indOfElement = (int)(indAndAdv[0]);
|
|
float advOfElement = indAndAdv[1];
|
|
|
|
/* This takes the medium between two intersecting commands, sometimes this is not wanted
|
|
if(advOfElement==1.0F){
|
|
int indNextCommand = (indOfElement + 1) % numCommands;
|
|
result = commands[indOfElement].getTangent(advOfElement);
|
|
RPoint tngNext = commands[indNextCommand].getTangent(0.0F);
|
|
result.add(tngNext);
|
|
result.scale(0.5F);
|
|
}else if (advOfElement==0.0F){
|
|
int indPrevCommand = (indOfElement - 1 + numCommands) % numCommands;
|
|
result = commands[indOfElement].getTangent(advOfElement);
|
|
RPoint tngPrev = commands[indPrevCommand].getTangent(1.0F);
|
|
result.add(tngPrev);
|
|
result.scale(0.5F);
|
|
}else{
|
|
result = commands[indOfElement].getTangent(advOfElement);
|
|
}
|
|
*/
|
|
|
|
return commands[indOfElement].getTangent(advOfElement);
|
|
}
|
|
|
|
|
|
/**
|
|
* Use this to return a specific tangent on the curve. It returns true if the point passed as a parameter is inside the path. Implementation taken from: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
|
|
* @param p the point for which to test containement..
|
|
* @return boolean, true if the point is in the path.
|
|
* */
|
|
public boolean contains(RPoint p){
|
|
float testx = p.x;
|
|
float testy = p.y;
|
|
|
|
// Test for containment in bounding box
|
|
RRectangle bbox = getBounds();
|
|
float xmin = bbox.getMinX();
|
|
float xmax = bbox.getMaxX();
|
|
|
|
float ymin = bbox.getMinY();
|
|
float ymax = bbox.getMaxY();
|
|
|
|
if( (testx < xmin) || (testx > xmax) || (testy < ymin) || (testy > ymax)){
|
|
return false;
|
|
}
|
|
|
|
// Test for containment in path
|
|
RPoint[] verts = getPoints();
|
|
|
|
if(verts == null){
|
|
return false;
|
|
}
|
|
|
|
int nvert = verts.length;
|
|
int i, j = 0;
|
|
boolean c = false;
|
|
for (i = 0, j = nvert-1; i < nvert; j = i++) {
|
|
if ( ((verts[i].y > testy) != (verts[j].y>testy)) &&
|
|
(testx < (verts[j].x-verts[i].x) * (testy-verts[i].y) / (verts[j].y-verts[i].y) + verts[i].x) )
|
|
c = !c;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
|
|
/**
|
|
* Use this to insert a split point into the path.
|
|
* @eexample insertHandle
|
|
* @param t float, the parameter of advancement on the curve. t must have values between 0 and 1.
|
|
* */
|
|
public void insertHandle(float t){
|
|
if((t == 0F) || (t == 1F)){
|
|
return;
|
|
}
|
|
|
|
float[] indAndAdv = indAndAdvAt(t);
|
|
int indOfElement = (int)(indAndAdv[0]);
|
|
float advOfElement = indAndAdv[1];
|
|
|
|
// Split the affected command and reconstruct each of the shapes
|
|
RCommand[] splittedCommands = commands[indOfElement].split(advOfElement);
|
|
|
|
if(splittedCommands[0] == null || splittedCommands[1] == null) {
|
|
return;
|
|
}
|
|
|
|
// Extract the splitted command
|
|
extract( indOfElement );
|
|
|
|
// Insert the splittedCommands
|
|
insert( splittedCommands[1], indOfElement );
|
|
insert( splittedCommands[0], indOfElement );
|
|
|
|
// Clear the cache
|
|
lenCurves = null;
|
|
lenCurve = -1F;
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Use this to insert a split point into each command of the path.
|
|
* @eexample insertHandleInPaths
|
|
* @param t float, the parameter of advancement on the curve. t must have values between 0 and 1.
|
|
* */
|
|
public void insertHandleInPaths(float t){
|
|
if((t == 0F) || (t == 1F)){
|
|
return;
|
|
}
|
|
|
|
int numCommands = countCommands();
|
|
|
|
for ( int i = 0; i<numCommands*2; i+=2 ) {
|
|
// Split the affected command and reconstruct each of the shapes
|
|
RCommand[] splittedCommands = commands[i].split(t);
|
|
|
|
if(splittedCommands[0] == null || splittedCommands[1] == null) {
|
|
return;
|
|
}
|
|
|
|
// Extract the splitted command
|
|
extract( i );
|
|
|
|
// Insert the splittedCommands
|
|
insert( splittedCommands[1], i );
|
|
insert( splittedCommands[0], i );
|
|
}
|
|
|
|
// Clear the cache
|
|
lenCurves = null;
|
|
lenCurve = -1F;
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Use this to split a path into two separate new paths.
|
|
* @eexample split
|
|
* @param t float, the parameter of advancement on the curve. t must have values between 0 and 1.
|
|
* @return RPath[], an array of two RPath.
|
|
* */
|
|
public RPath[] split(float t){
|
|
RPath[] result = new RPath[2];
|
|
|
|
int numCommands = countCommands();
|
|
if(numCommands == 0){
|
|
return null;
|
|
}
|
|
|
|
if(t==0.0F){
|
|
result[0] = new RPath();
|
|
result[1] = new RPath(this);
|
|
result[0].setStyle(this);
|
|
result[1].setStyle(this);
|
|
|
|
return result;
|
|
}
|
|
|
|
if(t==1.0F){
|
|
result[0] = new RPath(this);
|
|
result[1] = new RPath();
|
|
result[0].setStyle(this);
|
|
result[1].setStyle(this);
|
|
|
|
return result;
|
|
}
|
|
|
|
float[] indAndAdv = indAndAdvAt(t);
|
|
int indOfElement = (int)(indAndAdv[0]);
|
|
float advOfElement = indAndAdv[1];
|
|
|
|
|
|
// Split the affected command and reconstruct each of the shapes
|
|
RCommand[] splittedCommands = commands[indOfElement].split(advOfElement);
|
|
|
|
result[0] = new RPath();
|
|
for(int i = 0; i<indOfElement; i++){
|
|
result[0].addCommand(new RCommand(commands[i]));
|
|
}
|
|
result[0].addCommand(new RCommand(splittedCommands[0]));
|
|
result[0].setStyle(this);
|
|
|
|
result[1] = new RPath();
|
|
for(int i = indOfElement + 1; i < countCommands(); i++){
|
|
result[1].addCommand(new RCommand(commands[i]));
|
|
}
|
|
result[1].addCommand(new RCommand(splittedCommands[1]));
|
|
result[1].setStyle(this);
|
|
|
|
return result;
|
|
}
|
|
|
|
public void polygonize(){
|
|
RPoint[] points = getPoints();
|
|
|
|
if (points == null){
|
|
this.commands = null;
|
|
}else{
|
|
RPath result = new RPath(points[0]);
|
|
for(int i = 1; i< points.length; i++){
|
|
result.addLineTo(points[i]);
|
|
}
|
|
this.commands = result.commands;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Use this method to draw the path.
|
|
* @eexample drawPath
|
|
* @param g PGraphics, the graphics object on which to draw the path
|
|
*/
|
|
public void draw(PGraphics g){
|
|
countCommands();
|
|
|
|
// By default always draw with an adaptative segmentator
|
|
int lastSegmentator = RCommand.segmentType;
|
|
RCommand.setSegmentator(RCommand.ADAPTATIVE);
|
|
|
|
RPoint[] points = getPoints();
|
|
|
|
if(points == null){
|
|
return;
|
|
}
|
|
|
|
g.beginShape();
|
|
for(int i=0;i<points.length;i++){
|
|
g.vertex(points[i].x,points[i].y);
|
|
}
|
|
g.endShape(closed ? PConstants.CLOSE : PConstants.OPEN);
|
|
|
|
// Restore the user set segmentator
|
|
RCommand.setSegmentator(lastSegmentator);
|
|
}
|
|
|
|
public void draw(PApplet g){
|
|
countCommands();
|
|
|
|
// By default always draw with an adaptative segmentator
|
|
int lastSegmentator = RCommand.segmentType;
|
|
RCommand.setSegmentator(RCommand.ADAPTATIVE);
|
|
|
|
RPoint[] points = getPoints();
|
|
RCommand.setSegmentator(lastSegmentator);
|
|
if(points == null){
|
|
return;
|
|
}
|
|
g.beginShape();
|
|
for(int i=0;i<points.length;i++){
|
|
g.vertex(points[i].x,points[i].y);
|
|
}
|
|
g.endShape(closed ? PConstants.CLOSE : PConstants.OPEN);
|
|
|
|
// Restore the user set segmentator
|
|
RCommand.setSegmentator(lastSegmentator);
|
|
}
|
|
|
|
/**
|
|
* Use this method to add new commands to the contour.
|
|
* @eexample addCommand
|
|
* @invisible
|
|
*/
|
|
public void addCommand(RCommand p){
|
|
this.append(p);
|
|
|
|
lastPoint = commands[commands.length-1].endPoint;
|
|
}
|
|
|
|
/**
|
|
* Add a new cubic bezier to the path. The first point of the bezier will be the last point added to the path.
|
|
* @eexample addBezierTo
|
|
* @param cp1 first control point
|
|
* @param cp2 second control point
|
|
* @param end end point
|
|
*/
|
|
public void addBezierTo(RPoint cp1, RPoint cp2, RPoint end){
|
|
this.addCommand(RCommand.createBezier4(lastPoint, cp1, cp2, end));
|
|
}
|
|
|
|
/**
|
|
* Add a new cubic bezier to the path. The first point of the bezier will be the last point added to the path.
|
|
* @eexample addBezierTo
|
|
* @param cp1x the x coordinate of the first control point
|
|
* @param cp1y the y coordinate of the first control point
|
|
* @param cp2x the x coordinate of the second control point
|
|
* @param cp2y the y coordinate of the second control point
|
|
* @param endx the x coordinate of the end point
|
|
* @param endy the y coordinate of the end point
|
|
*/
|
|
public void addBezierTo(float cp1x, float cp1y, float cp2x, float cp2y, float endx, float endy){
|
|
RPoint cp1 = new RPoint(cp1x, cp1y);
|
|
RPoint cp2 = new RPoint(cp2x, cp2y);
|
|
RPoint end = new RPoint(endx, endy);
|
|
|
|
addBezierTo(cp1,cp2,end);
|
|
}
|
|
|
|
/**
|
|
* Add a new quadratic bezier to the path. The first point of the bezier will be the last point added to the path.
|
|
* @eexample addQuadTo
|
|
* @param cp1 first control point
|
|
* @param end end point
|
|
*/
|
|
public void addQuadTo(RPoint cp1, RPoint end){
|
|
this.addCommand(RCommand.createBezier3(lastPoint, cp1, end));
|
|
}
|
|
|
|
/**
|
|
* Add a new quadratic bezier to the path. The first point of the bezier will be the last point added to the path.
|
|
* @eexample addQuadTo
|
|
* @param cp1x the x coordinate of the first control point
|
|
* @param cp1y the y coordinate of the first control point
|
|
* @param endx the x coordinate of the end point
|
|
* @param endy the y coordinate of the end point
|
|
*/
|
|
public void addQuadTo(float cp1x, float cp1y, float endx, float endy){
|
|
RPoint cp1 = new RPoint(cp1x, cp1y);
|
|
RPoint end = new RPoint(endx, endy);
|
|
|
|
addQuadTo(cp1,end);
|
|
}
|
|
|
|
/**
|
|
* Add a new line to the path. The first point of the line will be the last point added to the path.
|
|
* @eexample addLineTo
|
|
* @param end end point
|
|
*/
|
|
public void addLineTo(RPoint end){
|
|
this.addCommand(RCommand.createLine(lastPoint, end));
|
|
}
|
|
|
|
/**
|
|
* Add a new line to the path. The first point of the line will be the last point added to the path.
|
|
* @eexample addLineTo
|
|
* @param endx the x coordinate of the end point
|
|
* @param endy the y coordinate of the end point
|
|
*/
|
|
public void addLineTo(float endx, float endy){
|
|
RPoint end = new RPoint(endx, endy);
|
|
addLineTo(end);
|
|
}
|
|
|
|
|
|
public void addClose(){
|
|
if(commands == null){
|
|
return;
|
|
}
|
|
|
|
if((commands[commands.length-1].endPoint.x == commands[0].startPoint.x) && (commands[commands.length-1].endPoint.y == commands[0].startPoint.y)) {
|
|
commands[commands.length-1].endPoint = new RPoint(commands[0].startPoint.x, commands[0].startPoint.y);
|
|
lastPoint = commands[commands.length-1].endPoint;
|
|
}else{
|
|
addLineTo(new RPoint(commands[0].startPoint.x,commands[0].startPoint.y));
|
|
}
|
|
|
|
closed = true;
|
|
}
|
|
|
|
/**
|
|
* @invisible
|
|
*/
|
|
public RPolygon toPolygon(){
|
|
return this.toShape().toPolygon();
|
|
}
|
|
|
|
/**
|
|
* @invisible
|
|
*/
|
|
public RShape toShape(){
|
|
return new RShape(this);
|
|
}
|
|
|
|
/**
|
|
* @invisible
|
|
*/
|
|
public RMesh toMesh(){
|
|
return this.toPolygon().toMesh();
|
|
}
|
|
|
|
/**
|
|
* Use this method to get the type of element this is.
|
|
* @eexample RPolygon_getType
|
|
* @return int, will allways return RGeomElem.POLYGON
|
|
*/
|
|
public int getType(){
|
|
return type;
|
|
}
|
|
|
|
|
|
public void print(){
|
|
for(int i=0;i<countCommands();i++){
|
|
String commandType = "";
|
|
switch(commands[i].commandType)
|
|
{
|
|
case RCommand.LINETO:
|
|
commandType = "LINETO";
|
|
break;
|
|
|
|
case RCommand.CUBICBEZIERTO:
|
|
commandType = "BEZIERTO";
|
|
break;
|
|
|
|
case RCommand.QUADBEZIERTO:
|
|
commandType = "QUADBEZIERTO";
|
|
break;
|
|
}
|
|
|
|
System.out.println("cmd type: " + commandType);
|
|
System.out.print("start point: ");
|
|
commands[i].startPoint.print();
|
|
System.out.print("\n");
|
|
System.out.print("end point: ");
|
|
commands[i].endPoint.print();
|
|
System.out.print("\n");
|
|
if(commands[i].controlPoints != null)
|
|
{
|
|
System.out.println("control points: ");
|
|
for(int j=0;j<commands[i].controlPoints.length;j++){
|
|
commands[i].controlPoints[j].print();
|
|
System.out.print(" ");
|
|
System.out.print("\n");
|
|
}
|
|
}
|
|
System.out.print("\n");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Use this method to transform the shape.
|
|
* @eexample RPath_transform
|
|
* @param m RMatrix, the matrix defining the affine transformation
|
|
* @related draw ( )
|
|
*/
|
|
// OPT: not transform the EndPoint since it's equal to the next StartPoint
|
|
/*
|
|
public void transform(RMatrix m){
|
|
RPoint[] ps = getHandles();
|
|
if(ps!=null){
|
|
for(int i=0;i<ps.length;i++){
|
|
ps[i].transform(m);
|
|
}
|
|
}
|
|
|
|
int numCommands = countCommands();
|
|
if(numCommands!=0){
|
|
commands[0].startPoint.transform(m);
|
|
for(int i=0;i<numCommands-1;i++){
|
|
for(int j=0;j<commands[i].countControlPoints();j++){
|
|
commands[i].controlPoints[j].transform(m);
|
|
}
|
|
commands[i].endPoint.transform(m);
|
|
}
|
|
}
|
|
|
|
}
|
|
*/
|
|
|
|
private float[] indAndAdvAt(float t){
|
|
int indOfElement = 0;
|
|
float[] lengthsCurves = getCurveLengths();
|
|
float lengthCurve = getCurveLength();
|
|
|
|
/* Calculate the amount of advancement t mapped to each command */
|
|
/* We use a simple algorithm where we give to each command the same amount of advancement */
|
|
/* A more useful way would be to give to each command an advancement proportional to the length of the command */
|
|
/* Old method with uniform advancement per command
|
|
float advPerCommand;
|
|
advPerCommand = 1F / numPaths;
|
|
indOfElement = (int)(Math.floor(t / advPerCommand)) % numPaths;
|
|
advOfElement = (t*numPaths - indOfElement);
|
|
*/
|
|
|
|
float accumulatedAdvancement = lengthsCurves[indOfElement] / lengthCurve;
|
|
float prevAccumulatedAdvancement = 0F;
|
|
|
|
/* Find in what command the advancement point is */
|
|
while(t > accumulatedAdvancement){
|
|
indOfElement++;
|
|
prevAccumulatedAdvancement = accumulatedAdvancement;
|
|
accumulatedAdvancement += (lengthsCurves[indOfElement] / lengthCurve);
|
|
}
|
|
|
|
float advOfElement = (t-prevAccumulatedAdvancement) / (lengthsCurves[indOfElement] / lengthCurve);
|
|
|
|
float[] indAndAdv = new float[2];
|
|
|
|
indAndAdv[0] = indOfElement;
|
|
indAndAdv[1] = advOfElement;
|
|
|
|
return indAndAdv;
|
|
}
|
|
|
|
private void append(RCommand nextcommand)
|
|
{
|
|
RCommand[] newcommands;
|
|
if(commands==null){
|
|
newcommands = new RCommand[1];
|
|
newcommands[0] = nextcommand;
|
|
}else{
|
|
newcommands = new RCommand[this.commands.length+1];
|
|
System.arraycopy(this.commands,0,newcommands,0,this.commands.length);
|
|
newcommands[this.commands.length]=nextcommand;
|
|
}
|
|
this.commands=newcommands;
|
|
}
|
|
|
|
private void insert(RCommand newcommand, int i) throws RuntimeException{
|
|
if( i < 0 ){
|
|
throw new RuntimeException("Negative values for indexes are not valid.");
|
|
}
|
|
|
|
RCommand[] newcommands;
|
|
if( commands == null ){
|
|
newcommands = new RCommand[1];
|
|
newcommands[0] = newcommand;
|
|
}else{
|
|
if( i > commands.length ){
|
|
throw new RuntimeException("Index out of the bounds. You are trying to insert an element with an index higher than the number of commands in the group.");
|
|
}
|
|
|
|
newcommands = new RCommand[this.commands.length + 1];
|
|
System.arraycopy( this.commands , 0 , newcommands , 0 , i );
|
|
newcommands[i] = newcommand;
|
|
System.arraycopy( this.commands , i , newcommands , i + 1 , this.commands.length - i);
|
|
}
|
|
this.commands = newcommands;
|
|
}
|
|
|
|
private void extract(int i) throws RuntimeException{
|
|
RCommand[] newcommands;
|
|
if(commands==null){
|
|
throw new RuntimeException("The group is empty. No commands to remove.");
|
|
}else{
|
|
if(i<0){
|
|
throw new RuntimeException("Negative values for indexes are not valid.");
|
|
}
|
|
if(i>commands.length-1){
|
|
throw new RuntimeException("Index out of the bounds of the group. You are trying to erase an element with an index higher than the number of commands in the group.");
|
|
}
|
|
if(commands.length==1){
|
|
newcommands = null;
|
|
}else if(i==0){
|
|
newcommands = new RCommand[this.commands.length-1];
|
|
System.arraycopy(this.commands,1,newcommands,0,this.commands.length-1);
|
|
}else if(i==commands.length-1){
|
|
newcommands = new RCommand[this.commands.length-1];
|
|
System.arraycopy(this.commands,0,newcommands,0,this.commands.length-1);
|
|
}else{
|
|
newcommands = new RCommand[this.commands.length-1];
|
|
System.arraycopy(this.commands,0,newcommands,0,i);
|
|
System.arraycopy(this.commands,i+1,newcommands,i,this.commands.length-i-1);
|
|
}
|
|
}
|
|
this.commands=newcommands;
|
|
}
|
|
}
|