Package cc.sketchchair.sketch

Source Code of cc.sketchchair.sketch.SketchPath

/*******************************************************************************
* This is part of SketchChair, an open-source tool for designing your own furniture.
*     www.sketchchair.cc
*    
*     Copyright (C) 2012, Diatom Studio ltd.  Contact: hello@diatom.cc
*
*     This program 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.
*
*     This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
//#IF JAVA
package cc.sketchchair.sketch;

import java.awt.geom.GeneralPath;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import cc.sketchchair.core.GLOBAL;
import cc.sketchchair.core.LOGGER;
import cc.sketchchair.core.PickBuffer;
import cc.sketchchair.core.SETTINGS;
import cc.sketchchair.functions.functions;
import cc.sketchchair.geometry.SlicePlane;
import cc.sketchchair.triangulate.Vector2D;

import nu.xom.Attribute;
import nu.xom.Element;
import ShapePacking.BezierControlNode;
import ShapePacking.spPoint;
import ShapePacking.spShape;

import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PGraphics;

import toxi.geom.Vec2D;
import toxi.geom.Vec3D;

/**
* SketchPath is the base class for most Sketch objects it contains low level functions for manipulating and calculating paths in SketchChair.
* @author gregsaul
*
*/
//#ENDIF JAVA

public class SketchPath extends SketchShape {
 
    // constants for using the bounding_box
    int MIN_X  = 0;
    int MIN_Y  = 1;
    int MAX_X = 2;
    int MAX_Y = 3;
   
   
   
  public List<SketchPoint> l = new ArrayList<SketchPoint>();
  boolean isOptimized;
  float pointDist = 5;//SETTINGS_SKETCH.dist_between_points; // distance between points
  // on the optimized line
  float offsetSize = 20;//SETTINGS_SKETCH.offset_size;
  SliceSlots slots = new SliceSlots();
  boolean slots_on_inside = true;

  int offsetType = 2;

  //private boolean open = false;

  boolean isOutline;
  private boolean cacheLength = false;
  private float cachedLength = -1;
  private float lastStepPercent;
  private int lastStepStartPos;
  private float lastStepLengthAlong;
  private float lastStepAlongBezier;
  private Vec2D lastStepMeasuredPoint;
  private float lastStepLenghtSegment;
  private boolean woundClockwise;
  private boolean woundClockwiseReset;
  private float cachedGetMaxX;
  private float cachedGetMaxY;
  private float cachedGetMinX;
  private float cachedGetMinY;

  static int OFFSET_LEFT = 0;
  static int OFFSET_RIGHT = 1;
  static int OFFSET_BOTH = 2;

  SketchPath(int offsetType, Sketch parentSketch) {
    super(parentSketch);
    this.offsetType = offsetType;

  }

  public SketchPath(Sketch parentSketch) {
    super(parentSketch);

  }

  //#IF JAVA
  public SketchPath(Sketch parentSketch, Element element) {
    super(parentSketch);
    //wrong type
    if (!element.getLocalName().equals("SketchPath"))
      return;

    if (element.getAttributeValue("id") != null) {
      this.setId(Integer.valueOf(element.getAttributeValue("id")));
    }

    if (element.getAttributeValue("closed") != null) {
      if (element.getAttributeValue("closed").equals("true")) {
        this.setClosed(true);
      } else {
        this.setClosed(false);
      }
    }

    //legacy fallback
    if (element.getAttributeValue("closed") == null)
      this.setClosed(true);

    if (element.getAttributeValue("isConstructionLine") != null) {
      //  this.setIsContructionLine(true);
    }

    if (element.getAttributeValue("union") != null) {
      this.union = Integer.valueOf(element.getAttributeValue("union"));
    }

    //for(int i = element.getChildCount()-1 ; i >= 0 ; i--){
    for (int i = 0; i < element.getChildCount(); i++) {
      Element child = (Element) element.getChild(i);
      if (child != null && child.getLocalName().equals("SketchPoint"))
        this.add(new SketchPoint(child));

    }

  }

  //#ENDIF JAVA

  public SketchPath(Sketch parentSketch, ArrayList<SketchPoint> outline) {
    super(parentSketch);
    for (int i = 0; i < outline.size(); i++) {
      SketchPoint p = outline.get(i);
      this.add(p);
    }
  }

  public void add(int i, SketchPoint point) {
    this.l.add(i, point);
    this.resetCachedVariables();
  }

  public void add(SketchPoint point) {
    this.l.add(point);
    this.resetCachedVariables();
  }

  public void addBezier(SketchPoint point, Vec2D controlP1, Vec2D controlP2) {
    //legacy code
    point.controlPoint1 = controlP1.copy();
    point.controlPoint2 = controlP2.copy();
  }

  public void build() {
    woudClockwiseReset();
  }

  //#IF JAVA
  /**
   * In special cases cache the length of our line to optimize calculations
   * @param cache
   */
  //#ENDIF JAVA

  public void cacheLength(boolean cache) {
    this.cacheLength = cache;
    this.cachedLength = -1;
  }

  public SketchPath clone() {
    SketchPath newSpline = new SketchPath(getParentSketch());

    newSpline.setClosed(this.getClosed());
    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint curPoint = this.l.get(i);
      SketchPoint point = curPoint.clone();//new SketchPoint(curPoint.x, curPoint.y);
      newSpline.l.add(point);
    }

    newSpline.slots = this.slots.clone();

    return newSpline;

  }

  public ArrayList<SketchPoint> cloneArray() {
    ArrayList<SketchPoint> loop = new ArrayList<SketchPoint>();
    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint v = this.l.get(i).clone();
      loop.add(v);
    } // TODO Auto-generated method stub
    return loop;
  }

  public boolean contains(SketchPoint vec) {
    return l.contains(vec);
  }

  public SketchShape copy(Sketch parentSketch) {
    SketchPath newSpline = new SketchPath(parentSketch);

    newSpline.setClosed(this.getClosed());
    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint curPoint = this.l.get(i);
      SketchPoint point = curPoint.clone();//new SketchPoint(curPoint.x, curPoint.y);
      newSpline.l.add(point);
    }

    newSpline.slots = this.slots.clone();
    newSpline.setId(this.getId());
    return newSpline;
  }

  public void destroy() {
    this.setDestroy(true);
  }

  public void flipHorizontal(toxi.geom.Vec3D centre) {
    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint curVec = this.l.get(i);
      curVec.x -= (curVec.x - centre.x) * 2;
      //curVec.y += (curVec.y - centre.y) * scale;

      if (curVec.containsBezier()) {

        if (curVec.controlPoint1 != null) {
          curVec.controlPoint1.x -= (curVec.controlPoint1.x - centre.x) * 2;
          //curVec.controlPoint1.y += (curVec.controlPoint1.y - centre.y) * scale;
        }

        if (curVec.controlPoint2 != null) {
          curVec.controlPoint2.x -= (curVec.controlPoint2.x - centre.x) * 2;
          //curVec.controlPoint2.y += (curVec.controlPoint2.y - centre.y) * scale;
        }
      }

    }

  }

  public SketchPoint get(int i) {

    return (SketchPoint)this.l.get(i);
  }

  public SketchPoint getCentreOfMass() {
    long x = 0;
    long y = 0;

    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint v = this.l.get(i);

      x += v.x;
      y += v.y;
    }
    return new SketchPoint(x / this.l.size(), y / this.l.size());

  }

  public SketchPoint getCentre() {

    return new SketchPoint(this.getMinX() + (this.getWidth() / 2),
        this.getMinY() + (this.getHeight() / 2));

  }

  private float getWidth() {
    return this.getMaxX() - this.getMinX();
  }

  private float getHeight() {
    return this.getMaxY() - this.getMinY();
  }

  public float getClosestPercent(float mouseX, float mouseY) {

    float closestDist = -1;
    float val = -1;
    this.cacheLength(true);
    SketchPoint mousePos = new SketchPoint(mouseX, mouseY);
    float step = SETTINGS_SKETCH.select_on_path_step / this.getlength();
    for (float i = 0; i <= 1; i += step) {
      Vec2D pos = this.getPos(i);

      if (i == 0 || pos.distanceTo(mousePos) < closestDist && pos != null) {
        val = i;
        closestDist = pos.distanceTo(mousePos);
      }

    }
    this.cacheLength(false);

    return val;
  }

  public SketchPoint getClosestPoint(SketchPoint pointOnPlan) {
    // TODO Auto-generated method stub
    return null;
  }

  public SketchPoint getClosestPoint(Vec2D pointOnPlan) {
    SketchPoint p = null;
    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint curVec = this.l.get(i);

      if (p == null
          || curVec.distanceTo(pointOnPlan) < p
              .distanceTo(pointOnPlan))
        p = curVec;
    }

    return p;

  }

  public Vec2D getClosestPointAlongPath(float x, float y) {

    float closestDist = -1;
    Vec2D closestPoint = null;
    this.cacheLength(true);
    this.resetPosStep();
    Vec2D mousePos = new SketchPoint(x, y);
    float step = SETTINGS_SKETCH.select_on_path_step / this.getlength();
    step = .01f;
    for (float i = 0; i < 1; i += step) {
      Vec2D pos = this.getPosStep(step);
      if ((closestDist == -1 && pos != null)
          || (pos != null && pos.distanceTo(mousePos) < closestDist)) {
        closestPoint = pos;
        closestDist = pos.distanceTo(mousePos);
      }

    }
    this.cacheLength(false);

    return closestPoint;
  }

  public Vec2D getClosestPointOnLine(float mouseX, float mouseY) {

    float closestDist = -1;
    float val = -1;
    Vec2D v = null;

    Vec2D mousePos = new SketchPoint(mouseX, mouseY);
    float step = SETTINGS_SKETCH.select_on_path_step / this.getlength();
    for (float i = 0; i < 1; i += step) {
      Vec2D pos = this.getPos(i);

      if (closestDist == -1 || pos.distanceTo(mousePos) < closestDist) {
        val = i;
        closestDist = pos.distanceTo(mousePos);
        v = pos;
      }

    }

    return v;
  }

  // id 0 gives color -2, etc.
  int getColor(int id) {
    return -(id + 2);
  }

  private int getIndex(SketchPoint p) {
    return this.l.indexOf(p);
  }

  public SketchPoint getLast() {

    if (this.l.size() > 0)
      return this.l.get(this.l.size() - 1);
    else
      return null;
  }
  public SketchPoint getFirst() {

    if (this.l.size() > 0)
      return this.l.get(0);
    else
      return null;
  }

  public float getlength() {

    if (this.cachedLength != -1 && this.cacheLength)
      return this.cachedLength;

    float length = 0;

    int offset = 0;

    if (!this.getClosed())
      offset = 1;

    this.cachedLength = this.getLengthTo(this.l.size() - offset);
    return this.cachedLength;

  }

  public int getLength() {
    return this.l.size();
  }

  public float getLengthBetween(int indexStart, int indexEnd) {
    float length = 0;

    Vec2D lastMeasured = null;
    for (int i = indexStart; i < indexEnd; i++) {
      SketchPoint curVec = this.l.get(i);
      SketchPoint nextVec = null;
      if (i < this.l.size() - 1)
        nextVec = this.l.get(i + 1);
      else
        nextVec = this.l.get(0);

      if (curVec.containsBezier() || nextVec.containsBezier()) {
        Vec2D bez1 = curVec;
        Vec2D bez2 = nextVec;

        if (curVec.containsBezier()) {
          bez1 = curVec.getControlPoint2();
        }

        if (nextVec.containsBezier()) {
          bez2 = nextVec.getControlPoint1();
        }

        lastMeasured = curVec.copy();
        float step = getParentSketch().getSketchGlobals().BEZIER_DETAIL_CALCULATIONS;
        ;
        for (float t = 0; t <= 1; t += step) {
          float x = functions.bezierPoint(curVec.x, bez1.x, bez2.x,
              nextVec.x, t);
          float y = functions.bezierPoint(curVec.y, bez1.y, bez2.y,
              nextVec.y, t);
          length += lastMeasured.distanceTo(new Vec2D(x, y));
          lastMeasured = new Vec2D(x, y);
        }
      } else {
        length += curVec.distanceTo(nextVec);
        lastMeasured = nextVec.copy();
      }

    }

    return length;

  }

  public float getlengthPerPercent() {
    return this.getlength() / 100;
  }

  public float getLengthTo(int index) {
    return getLengthBetween(0, index);
  }

  public ArrayList<SketchPoint> getList() {
    return (ArrayList<SketchPoint>) l;
  }

  public float getMaxX() {

    if (cachedGetMaxX != -1)
      return cachedGetMaxX;

    float maxX = 0;
    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint v = this.l.get(i);
      if (i == 0 || v.x > maxX)
        maxX = v.x;
    }
   
    for (int i = 1; i < this.l.size()-1; i++) {
      SketchPoint v = this.l.get(i-1);
      SketchPoint vNext = this.l.get(i);

      if(v.containsBezier() || vNext.containsBezier()){
        float[] bounds = calculate_standard_bbox(v.x, v.y, v.getControlPoint2().x,v.getControlPoint2().y,vNext.getControlPoint1().x,vNext.getControlPoint1().y,  vNext.x, vNext.y)
      if(bounds[MAX_X] > maxX)
        maxX = bounds[MAX_X];
      }
     
    }

   
    cachedGetMaxX = maxX;
    return maxX;
  }

  public float getMaxY() {
    if (cachedGetMaxY != -1)
      return cachedGetMaxY;

    float maxY = 0;
    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint v = this.l.get(i);
      if (i == 0 || v.y > maxY)
        maxY = v.y;

    }
   
   
    for (int i = 1; i < this.l.size()-1; i++) {
      SketchPoint v = this.l.get(i-1);
      SketchPoint vNext = this.l.get(i);

      if(v.containsBezier() || vNext.containsBezier()){
        float[] bounds = calculate_standard_bbox(v.x, v.y, v.getControlPoint2().x,v.getControlPoint2().y,vNext.getControlPoint1().x,vNext.getControlPoint1().y,  vNext.x, vNext.y)
      if(bounds[MAX_Y] > maxY)
        maxY = bounds[MAX_Y];
      }
     
    }
   
   
   
    cachedGetMaxY = maxY;
    return maxY;
  }

  public float getMinX() {

    if (cachedGetMinX != -1)
      return cachedGetMinX;

    float minX = 0;
    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint v = this.l.get(i);
      if (i == 0 || v.x < minX)
        minX = v.x;
    }
   
   
    for (int i = 1; i < this.l.size()-1; i++) {
      SketchPoint v = this.l.get(i-1);
      SketchPoint vNext = this.l.get(i);

      if(v.containsBezier() || vNext.containsBezier()){
        float[] bounds = calculate_standard_bbox(v.x, v.y, v.getControlPoint2().x,v.getControlPoint2().y,vNext.getControlPoint1().x,vNext.getControlPoint1().y,  vNext.x, vNext.y)
      if(bounds[MIN_X] < minX)
        minX = bounds[MIN_X];
      }
     
    }
   
   
   
    cachedGetMinX = minX;
    return minX;
  }

  public float getMinY() {

    if (cachedGetMinY != -1)
      return cachedGetMinY;

    float minY = 0;
    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint v = this.l.get(i);
      if (i == 0 || v.y < minY)
        minY = v.y;
    }
   
   
    for (int i = 1; i < this.l.size()-1; i++) {
      SketchPoint v = this.l.get(i-1);
      SketchPoint vNext = this.l.get(i);

      if(v.containsBezier() || vNext.containsBezier()){
        float[] bounds = calculate_standard_bbox(v.x, v.y, v.getControlPoint2().x,v.getControlPoint2().y,vNext.getControlPoint1().x,vNext.getControlPoint1().y,  vNext.x, vNext.y)
      if(bounds[MIN_Y] < minY)
        minY = bounds[MIN_Y];
      }
     
    }
   
   
   
    cachedGetMinY = minY;
    return minY;
  }
 
 
    /**
     * Calculate the bezier value for one dimension at distance 't'
     */
    float calculate_bezier(float t, float p0, float p1, float p2, float p3) {
      float mt = (1-t);
      return (mt*mt*mt*p0) + (3*mt*mt*t*p1) + (3*mt*t*t*p2) + (t*t*t*p3); }
   
    /**
     * expand the x-bounds, if the value lies outside the bounding box
     */
    void exandXBounds(float[] bounds, float value) {
      if(bounds[MIN_X] > value) { bounds[MIN_X] = value; }
      else if(bounds[MAX_X] < value) { bounds[MAX_X] = value; }}

    /**
     * expand the x-bounds, if the value lies outside the bounding box
     */
    void exandYBounds(float[] bounds, float value) {
      if(bounds[MIN_Y] > value) { bounds[MIN_Y] = value; }
      else if(bounds[MAX_Y] < value) { bounds[MAX_X] = value; }}

   
    /**
     * Calculate the bounding box for this bezier curve. The next bit is technical. See the comment on this topic on
     * http://newsgroups.derkeiler.com/Archive/Comp/comp.graphics.algorithms/2005-07/msg00334.html
     * and the worked out mathematics at http://pomax.nihongoresources.com/downloads/bezierbounds.html
     * for an explanation of why the following code is being used, and why it works.
     */
 
  float[] calculate_standard_bbox(float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2)
    {
      // compute linear bounds first
      float[] bounds = {Math.min(x1,x2), Math.min(y1,y2), Math.max(x1,x2), Math.max(y1,y2)};
   
      float dcx0 = cx1 - x1;
      float dcy0 = cy1  -y1;
      float dcx1 = cx2 - cx1;
      float dcy1 = cy2 - cy1;
      float dcx2 = x2 - cx2;
      float dcy2 = y2 - cy2;
   
      // Recompute bounds projected on the x-axis, if the control points lie outside the bounding box x-bounds
      if(cx1<bounds[MIN_X] || cx1>bounds[MAX_X] || cx2<bounds[MIN_X] || cx2>bounds[MAX_X]) {
        // we don't need to do this, but 'a', 'b' and 'c' are easier to read.
        float a = dcx0; float b = dcx1; float c = dcx2;
   
        // Do we have a problematic discriminator if we use these values?
        // If we do, because we're computing at sub-pixel level anyway, simply salt 'b' a tiny bit.
        if(a+c != 2*b) { b+=0.01; }
   
        float numerator = 2*(a - b);
        float denominator = 2*(a - 2*b + c);
        float quadroot = (2*b-2*a)*(2*b-2*a) - 2*a*denominator;
        float root = (float) Math.sqrt(quadroot);
   
        // there are two possible values for 't' that yield inflection points,
        // and each of these inflection points might be outside the linear bounds
        float t1 =  (numerator + root) / denominator;
        float t2 =  (numerator - root) / denominator;
   
        // so, which of these is the useful point? (remember that t must lie
        // in [0,1], and that t=0 and t=1 are already the linear bounds)
        if(0<t1 && t1<1) { exandXBounds(bounds, calculate_bezier(t1, x1, cx1, cx2, x2)); }
        if(0<t2 && t2<1) { exandXBounds(bounds, calculate_bezier(t2, x1, cx1, cx2, x2)); }
      }

      // Recompute bounds projected on the y-axis, if the control points lie outside the bounding box
      // y-bounds. No comments this time, because the code is the same, just with 'y' instead of 'x'
      if(cy1<bounds[MIN_Y] || cy1>bounds[MAX_Y] || cy2<bounds[MIN_Y] || cy2>bounds[MAX_Y]) {
        float a = dcy0; float b = dcy1; float c = dcy2;
        if(a+c != 2*b) { b+=0.01; }
        float numerator = 2*(a - b);
        float denominator = 2*(a - 2*b + c);
        float quadroot = (2*b-2*a)*(2*b-2*a) - 2*a*denominator;
        float root = (float) Math.sqrt(quadroot);
        float t1 =  (numerator + root) / denominator;
        float t2 =  (numerator - root) / denominator;
        if(0<t1 && t1<1) { exandYBounds(bounds, calculate_bezier(t1, y1, cy1, cy2, y2)); }
        if(0<t2 && t2<1) { exandYBounds(bounds, calculate_bezier(t2, y1, cy1, cy2, y2)); }
      }

      // and we're done, form this box's rectangle and return
      float[] bbox = {bounds[0],bounds[1], bounds[2],bounds[3] };
      return bbox;
    }

  public SketchPoint getNodeBetween(float t1, float t2) {

    if (t1 > 1 || t1 < 0 || t2 > 1 || t2 < 0)
      return null;

    float totalLen = this.getlength();
    float destLen1 = totalLen * t1;
    float destLen2 = totalLen * t2;
    float length = 0;

    // /-------------------------------------------------------------------------------------------------------
    // SLOTS ON INSIDE
    // --------------------------------------------------------------------------------------------------------

    for (int i = 0; i < this.l.size() - 1; i++) {
      SketchPoint curVec = this.l.get(i);
      SketchPoint nextVec = this.l.get(i + 1);
      length += curVec.distanceTo(nextVec);

      if (length > destLen1) {
        if (length < destLen2)
          return nextVec;
        else
          return null;

      }

    }

    return null;
  }

  public GeneralPath getOutlineGeneralPath() {
    GeneralPath gPath = new GeneralPath();

    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint curVec = null;
      SketchPoint nextVec = null;

      if (i >= this.l.size() - 1) {
        nextVec = this.l.get(0);
      } else {
        nextVec = this.l.get(i + 1);
      }

      curVec = this.l.get(i);

      if (i == 0) {
        gPath.moveTo(curVec.x, curVec.y);
        // gPath.lineTo(curVec.x, curVec.y);
      }
      if (i == this.l.size() - 1) {
        gPath.moveTo(curVec.x, curVec.y);
        // gPath.lineTo(curVec.x, curVec.y);
      }

      if (curVec.containsBezier() || nextVec.containsBezier()) {

        // gPath.lineTo(vec.x, vec.y);
        // System.out.println(i + " curveTo " + vec + " " + bc1.c1 + " "
        // + bc1.c2+ " " + bc2.c1 + " " + bc2.c2);
        //gPath.lineTo(nextVec.x, nextVec.y);

        gPath.curveTo(curVec.getControlPoint2().x,
            curVec.getControlPoint2().y,
            nextVec.getControlPoint1().x,
            nextVec.getControlPoint1().y, nextVec.x, nextVec.y);
      } else {
        // System.out.println(i + "lineTo " + vec );
        gPath.lineTo(nextVec.x, nextVec.y);

      }
    }

    return gPath;

  }

  public SketchShape getOverShape(float x, float y) {
    Vec2D closePoint = getClosestPointAlongPath(x, y);
    if (closePoint != null
        && closePoint.distanceTo(new Vec2D(x, y)) < SETTINGS_SKETCH.SELECT_EDGE_DIST) {
      this.lastMouseOverVec = closePoint;
      this.lastMouseOverPercent = getClosestPercent(x, y);
      return this;
    }
    return null;
  }

  public SketchPath getPath() {
    return this;

  }

  public float getPercent(SketchPoint p) {
    int index = getIndex(p);
    return getLengthTo(index) / this.getlength();
  }

  public Vec2D getPerpendicular(float percent) {

    Vec2D vecBefore = null;
    Vec2D vecAfter = null;

    float offsetSearch = .01f;

    if (percent - offsetSearch < 0)
      vecBefore = this.getPos((percent));
    else
      vecBefore = this.getPos((percent - offsetSearch));

    if (percent + offsetSearch > 1)
      vecAfter = this.getPos((percent));
    else
      vecAfter = this.getPos((percent + offsetSearch));

    if (vecAfter == null)
      return null;

    vecAfter = (Vec2D) vecAfter.sub(vecBefore);
    vecAfter.normalize();
    // SketchPoint newAn = vecAfter.getRotated((float)(Math.PI/2));

    if (WoundClockwise())
      vecAfter = vecAfter.rotate((float) Math.PI);

    return vecAfter;
  }

  // /-------------------------------------------------------------------------------------------------------
  // GET POS
  // --------------------------------------------------------------------------------------------------------
  public Vec2D getPos(float percent) {

    if (percent > 1 || percent < 0 || this.l.size() < 1)
      return null;

    float totalLen = this.getlength();
    float destLen = totalLen * percent;

    float length = 0;
    int offset = 0;

    if (!this.getClosed())
      offset = 1;

    Vec2D lastMeasuredPoint = (Vec2D) this.l.get(0);
    for (int i = 0; i < this.l.size() - offset; i++) {
      SketchPoint curP = this.l.get(i);
      SketchPoint nextP = null;
      if (i < this.l.size() - 1)
        nextP = this.l.get(i + 1);
      else
        nextP = this.l.get(0);

      if (curP.containsBezier() || nextP.containsBezier()) {

        float bezStep = getParentSketch().getSketchGlobals().BEZIER_DETAIL_CALCULATIONS;
        for (float t = bezStep; t < 1; t += bezStep) {
          float x = functions.bezierPoint(curP.x,
              curP.getControlPoint2().x,
              nextP.getControlPoint1().x, nextP.x, t);
          float y = functions.bezierPoint(curP.y,
              curP.getControlPoint2().y,
              nextP.getControlPoint1().y, nextP.y, t);

          length += lastMeasuredPoint.distanceTo(new Vec2D(x, y));
          lastMeasuredPoint = new Vec2D(x, y);

          if (length >= destLen)
            return new SketchPoint(x, y);

        }

      } else {
        length += curP.distanceTo(nextP);
        lastMeasuredPoint = nextP.copy();

        //LOGGER.info("percent "+percent+ " totalLen " + totalLen + " destLen "+ destLen + " length " + length);

        if (length > destLen) {

          float segLen = curP.distanceTo(nextP);
          float lastLen = length - segLen;
          float curPos = destLen - lastLen;
          float t = curPos / segLen;

          float x = curP.x + ((nextP.x - curP.x) * t);
          float y = curP.y + ((nextP.y - curP.y) * t);

          return new SketchPoint(x, y);

        }

      }

    }

    return null;
  }

  public Vec2D getPos(int index, float percent) {

    if (percent > 1 || percent < 0)
      return null;

    SketchPoint curP = this.l.get(index);
    SketchPoint nextP = this.l.get(index + 1);

    if (curP.containsBezier() || nextP.containsBezier()) {
      Vec2D bez1 = curP;
      Vec2D bez2 = nextP;

      if (curP.containsBezier()) {
        bez1 = curP.getControlPoint2();
      }

      if (nextP.containsBezier()) {
        bez2 = nextP.getControlPoint1();
      }

      float x = functions.bezierPoint(curP.x, bez1.x, bez2.x, nextP.x,
          percent);
      float y = functions.bezierPoint(curP.y, bez1.y, bez2.y, nextP.y,
          percent);

      /*
       * To find the real point along a spline we will need to set though each percentage (t) until we find the correct len then filter back
       */

      return new Vec2D(x, y);
    } else {
      float x = curP.x + ((nextP.x - curP.x) * percent);
      float y = curP.y + ((nextP.y - curP.y) * percent);

      return new Vec2D(x, y);
    }
  }

  public Vec2D getPosApprox(SketchPoint curP, SketchPoint nextP, float percent) {

    if (percent > 1 || percent < 0)
      return null;
    if (curP.containsBezier() || nextP.containsBezier()) {

      float x = functions.bezierPoint(curP.x, curP.getControlPoint2().x,
          nextP.getControlPoint1().x, nextP.x, percent);
      float y = functions.bezierPoint(curP.y, curP.getControlPoint2().y,
          nextP.getControlPoint1().y, nextP.y, percent);
      return new SketchPoint(x, y);

    } else {
      float x = curP.x + ((nextP.x - curP.x) * percent);
      float y = curP.y + ((nextP.y - curP.y) * percent);
      return new SketchPoint(x, y);

    }

  }

  public int getPosIndex(float percent) {

    if (percent > 1 || percent < 0)
      return 0;

    float totalLen = this.getlength();
    float destLen = totalLen * percent;

    int offset = 0;

    if (this.getClosed())
      offset = 1;

    for (int i = 0; i < this.l.size() + offset; i++) {
      if (getLengthBetween(0, i) > destLen)
        return i - 1;

    }

    return 0;
  }

  // /-------------------------------------------------------------------------------------------------------
  // GET THE POSITION A CENTAIN PERCENT ALONG THE PATH!
  // --------------------------------------------------------------------------------------------------------
  public Vec2D getPosStep(float step) {

    lastStepPercent += step;

    float percent = lastStepPercent;

    if (percent > 1 || percent < 0)
      return null;

    float totalLen = this.getlength();

    float destLen = totalLen * percent; //|----------/----------| = .5f = total len * .5f

    float lengthToCurrentPos = lastStepLengthAlong; // what length was the previous part found at?

    int offset = 0;

    if (!this.getClosed())
      offset = 1;

    if (lastStepMeasuredPoint == null && this.l.size() > 0)
      lastStepMeasuredPoint = (Vec2D) this.l.get(0);

    for (int i = lastStepStartPos; i < this.l.size() - offset; i++) {
      SketchPoint curP = this.l.get(i);
      SketchPoint nextP = null;

      //loop around
      if (i < this.l.size() - 1)
        nextP = this.l.get(i + 1);
      else
        nextP = this.l.get(0);

      if (curP.containsBezier() || nextP.containsBezier()) {

        //LOGGER.info("percent "+percent+ " totalLen " + totalLen + " destLen "+ destLen + " lengthToCurrentPos " + lengthToCurrentPos);

        float bezStep = getParentSketch().getSketchGlobals().BEZIER_DETAIL_CALCULATIONS;
        for (float t = bezStep + lastStepAlongBezier; t < 1; t += bezStep) {

          float x = functions.bezierPoint(curP.x,
              curP.getControlPoint2().x,
              nextP.getControlPoint1().x, nextP.x, t);
          float y = functions.bezierPoint(curP.y,
              curP.getControlPoint2().y,
              nextP.getControlPoint1().y, nextP.y, t);

          //if(lastStepMeasuredPoint.distanceTo(new Vec2D(x,y)) < 100)
          //LOGGER.info("add this" + lastStepMeasuredPoint.distanceTo(new Vec2D(x,y)) + " percent " + percent );

          lengthToCurrentPos += lastStepMeasuredPoint
              .distanceTo(new Vec2D(x, y));
          lastStepMeasuredPoint = new Vec2D(x, y);

          if (lengthToCurrentPos > destLen) {
            //  lastStepStartPos = i; //remember
            lastStepAlongBezier = t;
            lastStepLengthAlong = lengthToCurrentPos;
            lastStepStartPos = i;

            //we need to find the actual intersection point here!
            return new SketchPoint(x, y);
          }

        }
        lastStepAlongBezier = 0; //gone though one segment and did not fidn a intersection so reset

      } else {

        //LOGGER.info("HERE AT ALL");

        lengthToCurrentPos += curP.distanceTo(nextP);
        //  LOGGER.info("percent "+percent+ " totalLen " + totalLen + " destLen "+ destLen + " lengthToCurrentPos " + lengthToCurrentPos);

        if (lengthToCurrentPos > destLen) {
          //    LOGGER.info("percent "+percent+ " totalLen " + totalLen + " destLen "+ destLen + " length " + length);

          float segLen = curP.distanceTo(nextP);
          float lastLen = lengthToCurrentPos - segLen;
          float curPos = destLen - lastLen;
          float t = curPos / segLen;
          //  LOGGER.info("t"+t);

          float x = curP.x + ((nextP.x - curP.x) * t);
          float y = curP.y + ((nextP.y - curP.y) * t);
          lengthToCurrentPos -= (segLen);
          lastStepLengthAlong = lengthToCurrentPos;
          lastStepStartPos = i;
          lastStepMeasuredPoint = nextP.copy();

          return new SketchPoint(x, y);

        } else {
          //  lastStepStartPos = ; //remember
        }

      }

      lastStepAlongBezier = 0; //reset to start of bezier

    }

    return null;

  }

  public Vec2D getPosStep(SketchPoint curP, SketchPoint nextP, float step) {

    lastStepPercent += step;
    float percent = lastStepPercent;

    if (percent > 1 || percent < 0)
      return null;

    if (lastStepLenghtSegment == 0)
      lastStepLenghtSegment = this.getLengthBetween(this.getIndex(curP),
          this.getIndex(nextP));

    float totalLen = lastStepLenghtSegment;

    float destLen = totalLen * percent;

    float length = lastStepLengthAlong;

    if (lastStepMeasuredPoint == null)
      lastStepMeasuredPoint = (Vec2D) curP.copy();

    if (curP.containsBezier() || nextP.containsBezier()) {

      float bezStep = getParentSketch().getSketchGlobals().BEZIER_DETAIL_CALCULATIONS;
      for (float t = lastStepAlongBezier; t <= 1; t += bezStep) {
        float x = functions.bezierPoint(curP.x,
            curP.getControlPoint2().x, nextP.getControlPoint1().x,
            nextP.x, t);
        float y = functions.bezierPoint(curP.y,
            curP.getControlPoint2().y, nextP.getControlPoint1().y,
            nextP.y, t);

        length += lastStepMeasuredPoint.distanceTo(new Vec2D(x, y));
        lastStepMeasuredPoint = new Vec2D(x, y);

        if (length > destLen) {
          /*
          LOGGER.info(" t " + t +
              " percent " + percent +
              " totalLen " + totalLen +
              " lastStepAlongBezier " + lastStepAlongBezier  +
              " lastStepLengthAlong " + lastStepLengthAlong);
          */
          lastStepAlongBezier = t;
          lastStepLengthAlong = length;
          return new SketchPoint(x, y);
        }

      }

    } else {

      if (length > destLen) {

        float segLen = curP.distanceTo(nextP);
        float lastLen = length - segLen;
        float curPos = destLen - lastLen;
        float t = curPos / segLen;

        float x = curP.x + ((nextP.x - curP.x) * t);
        float y = curP.y + ((nextP.y - curP.y) * t);

        lastStepLengthAlong = length;
        return new SketchPoint(x, y);

      } else {
        length += curP.distanceTo(nextP);
        lastStepMeasuredPoint = nextP.copy();

      }

    }
    lastStepAlongBezier = 0; //reset to start of bezier
    return null;

  }

  public SketchPoint getSketchPointpickBuffer(int col) {

    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint curVec = this.l.get(i);
      if (col == getColor(i + (this.id * 100)))
        return curVec;
    }
    return null;
  }

  public SketchPoint getVec2DpickBuffer(int col) {
    // TODO Auto-generated method stub
    return null;
  }

  public ArrayList<Vector2D> getVectorLoop() {
    if (this.l.size() < 2)
      return null;

    ArrayList<Vector2D> loop = new ArrayList<Vector2D>();
    Vec2D prevVec = null;
    if (SETTINGS_SKETCH.build_collision_mesh_detailed) {

      float step = SETTINGS_SKETCH.build_collision_mesh_res
          / this.getlength();

      for (float t = 0; t < 1; t += step) {
        Vec2D v = this.getPos(t);

        if (t != 0) {
          SketchPoint nodeBetween = this.getNodeBetween(t - step, t);
          if (nodeBetween != null) {
            loop.add(new Vector2D(nodeBetween.x, nodeBetween.y));
          }

        }

        if (prevVec == null || v.x != prevVec.x && v.y != prevVec.y
            && v != null) {
          prevVec = v;
          loop.add(new Vector2D(v.x, v.y));
        }

      }

    } else {
      for (int i = 1; i < this.l.size(); i++) {
        SketchPoint v = this.l.get(i);
        if (prevVec == null || v.x != prevVec.x && v.y != prevVec.y) {
          prevVec = v;
          loop.add(new Vector2D(v.x, v.y));
        }

      }
    }

    return loop;
  }

  public int indexOf(SketchPoint vec) {
    return l.indexOf(vec);
  }

  public void insertPoint(SketchPoint closestPoint) {
    int index = this.getPosIndex(this.getClosestPercent(closestPoint.x,
        closestPoint.y));

    //System.out.println(this.getClosestPercent(closestPoint.x, closestPoint.y) +"DANGER1");
    this.l.add(index + 1, closestPoint);

  }

  public boolean intersects(SketchPath path) {
    if (intersectsCount(path) > 0)
      return true;
    else
      return false;

  }
 
 
 
  public boolean inside(SketchPath path) {
   
    SketchPoint firstPoint = this.get(0);
    if(path.inside(firstPoint))
      return true;
    else
      return false;

  }
 
 
  public boolean inside(SketchPoint point) {
   
    float x1max = this.getMaxX();
    float x1min = this.getMinX();
    float y1max = this.getMaxY();
    float y1min = this.getMinY();

    float x2max = point.x;
    float x2min = point.x;
    float y2max = point.y;
    float y2min = point.y;

    if ((x1min < x2max) && (x1max > x2min) && (y1min < y2max)
        && (y1max > y2min)){
        return this.pointInPolygon(point);

    }else{
      return false;
    }

  }

  public int intersectsCount(SketchPath path2) {
    float x1max = this.getMaxX();
    float x1min = this.getMinX();
    float y1max = this.getMaxY();
    float y1min = this.getMinY();

    float x2max = path2.getMaxX();
    float x2min = path2.getMinX();
    float y2max = path2.getMaxY();
    float y2min = path2.getMinY();

    if ((x1min < x2max) && (x1max > x2min) && (y1min < y2max)
        && (y1max > y2min)

    ) {

      return intersectsPath(path2);

      //return true;
    } else {
      return 0;
    }
    /*
     * } for (int p1 = 0; p1 < this.l.size()-1; p1++) { SketchPoint v = (SketchPoint)
     * this.l.get(p1); SketchPoint newVec = new SketchPoint(v.x, v.y);
     */

  }

  int intersectsPath(SketchPath otherPath) {
    int intersectCount = 0;

    int loop = 0;
    if (this.getClosed())
      loop = 1;

    int loop2 = 0;

    if (otherPath.getClosed())
      loop2 = 1;

    for (int i = 1; i < this.l.size() + loop; i++) {
      SketchPoint curVec = null;
      SketchPoint preVec = null;

      // --- last or first point ---
      if (i == 1) {
        curVec = this.l.get(0);
      }

      if (i >= 1)
        preVec = this.l.get(i - 1);

      if (i == this.l.size()) {
        curVec = this.l.get(0);
        preVec = this.l.get(this.l.size() - 1);
      } else {
        curVec = this.l.get(i);
      }

      for (int i2 = 1; i2 < otherPath.l.size() + loop2; i2++) {
        SketchPoint curVec2 = null;
        SketchPoint preVec2 = null;

        // --- last or first point ---
        if (i2 == 1) {
          curVec2 = otherPath.l.get(0);
        }

        if (i2 >= 1)
          preVec2 = otherPath.l.get(i2 - 1);

        if (i2 == otherPath.l.size()) {
          curVec2 = otherPath.l.get(0);
          preVec2 = otherPath.l.get(otherPath.l.size() - 1);
        } else {
          curVec2 = otherPath.l.get(i2);
        }

        if (functions.intersect(curVec.x, curVec.y, preVec.x, preVec.y,
            curVec2.x, curVec2.y, preVec2.x, preVec2.y) == functions.DO_INTERSECT)
          intersectCount++;
      }

    }
    return intersectCount;

  }

  public boolean isPointInside(Vec2D p) {
    return this.pointInPolygon(p);
    // return this.path.isPointInside(p);
  }

  public void mouseDragged(float mouseX, float mouseY) {

    Vec2D pointOnPlane = new Vec2D(mouseX, mouseY);
    if ((getParentSketch().getSketchTools().getCurrentTool() == SketchTools.SELECT_TOOL || getParentSketch()
        .getSketchTools().getCurrentTool() == SketchTools.SELECT_BEZIER_TOOL)
        && getParentSketch().getSketchTools().getMouseButton() == SketchTools.MOUSE_LEFT) {

      for (int i = 0; i < this.getSelectedNodes().size(); i++) {
        Object o = this.getSelectedNodes().get(i);

        if (o instanceof SketchPoint) {
          SketchPoint v = (SketchPoint) o;
          if (v.containsBezier()) {

            Vec2D delta = v.sub(pointOnPlane);
            //System.out.println(pointOnPlane);

            v.controlPoint1.subSelf(delta);
            v.controlPoint2.subSelf(delta);

          }
          v.set(pointOnPlane.x, pointOnPlane.y);

        }

        //is a bezier point your dragging
        if (o instanceof Vec2D) {
          Vec2D v = (Vec2D) o;
          v.x = pointOnPlane.x;
          v.y = pointOnPlane.y;

          //if dragging with the select bezier tool also mirror the opposite bezzier
          if (getParentSketch().getSketchTools().getCurrentTool() == SketchTools.SELECT_BEZIER_TOOL) {
            //Vec2D otherBez = (Vec2D)o;

          }

        }

        this.resetCachedVariables(); //forget cached length

      }
    }

  }

  public void mouseReleased(float mouseX, float mouseY) {
    //if(getParentSketch().getSketchTools().getMouseButton() == PConstants.RIGHT)
    //  this.closed = true;
  }

  public void movePoint(SketchPoint selectedVec, Vec2D planePoint) {
    // TODO Auto-generated method stub

  }

  public void offset() {

  }

  public void optimize() {
    /*
     * if(this.l.size() < 3) return;
     *
     * SketchPoint lastStoredPoint = (SketchPoint) this.l.get(0); List optimizedArray =
     * new ArrayList(); optimizedArray.add(lastStoredPoint);
     *
     * float step = SETTINGS_SKETCH.spline_point_every / this.getlength(); for
     * (float i = 0; i < 1; i += step) {
     *
     * optimizedArray.add(this.getPos(i)); }
     *
     * this.l = optimizedArray;
     */
    // this.offset();
  }

  // does this Polygon contain the point p?
  // if p is on boundary then 0 or 1 is returned, and p is in exactly one
  // point of every partition of plane
  // Reference: http://exaflop.org/docs/cgafaq/cga2.html
  public boolean pointInPolygon(Vec2D p) {
    int crossings = 0;
    for (int i = 0; i < this.l.size() - 1; i++) {
      SketchPoint curVec = this.l.get(i);
      SketchPoint nextVec = this.l.get(i + 1);
      double slope = (nextVec.x - curVec.x) / (nextVec.y - curVec.y);
      boolean cond1 = (curVec.y <= p.y) && (p.y < nextVec.y);
      boolean cond2 = (nextVec.y <= p.y) && (p.y < curVec.y);
      boolean cond3 = p.x < slope * (p.y - curVec.y) + curVec.x;
      if ((cond1 || cond2) && cond3)
        crossings++;
    }
    return (crossings % 2 != 0);
  }

  public void remove(int i) {
    this.l.remove(i);
    this.resetCachedVariables();
  }

  public void remove(SketchPoint point) {
    l.remove(point);
  }

  public void removeVertex(SketchPoint v) {
    if (this.getClosed() && this.l.contains(v))
      this.l.remove(v);

    if (this.l.size() == 0)
      this.destroy();

  }

  public void render(PGraphics g) {
   
    if (this.isOutline) {
      //return;
     
    }
   
   
    //TODO:SET according to render mode

    //if(isConstructionLine())
    //  return;

    //if (!this.getClosed() && !isConstructionLine())
    //  this.select();

    //if we are on a selected layer
    if (!getParentSketch().screenshot
        && this.getParentSketch().getLayerSelected()) {
      g.fill(SETTINGS_SKETCH.SKETCHSHAPE_FILL_SELECTED_COLOUR);
      g.strokeWeight(SETTINGS_SKETCH.SKETCHSHAPE_FILL_SELECTED_WEIGHT);
      g.stroke(SETTINGS_SKETCH.SKETCHSHAPE_PATH_COLOUR_SELECTED);
      //g.stroke(SETTINGS_SKETCH.SKETCHSHAPE_PATH_COLOUR_UNSELECTED); 
    } else {

      //don't render details on unselected layers!
      g.noStroke();
      //g.noFill();
      g.fill(SETTINGS_SKETCH.SKETCHSHAPE_FILL_UNSELECTED_COLOUR);
      //g.strokeWeight(SETTINGS_SKETCH.SKETCHOUTLINE_UNSELECTED_WEIGHT);
      //g.stroke(SETTINGS_SKETCH.SKETCHSHAPE_PATH_COLOUR_UNSELECTED);

    }

    // RENDER MODES
    switch (getParentSketch().getRenderMode()) {

    //#IF JAVA
    /*  3D preview
     *  Used to render the 3D preview in the top right corner of the screen
     */
    //#ENDIF JAVA

    case Sketch.RENDER_3D_PREVIW:
     
      //don't review guides
      //if(getParentSketch().getOnSketchPlane().guide)
      //return;


      if (this.getParentSketch().getLayerSelected())
        g.fill(SETTINGS_SKETCH.SKETCHSHAPE_FILL_SELECTED_DIAGRAM_COLOUR);
      else
        g.fill(SETTINGS_SKETCH.SKETCHSHAPE_FILL_DIAGRAM_COLOUR);

      g.noStroke();

      if (this.isOutline) {
        g.stroke(SETTINGS_SKETCH.SKETCHOUTLINE_PATH_COLOUR_DIAGRAM);
        g.strokeWeight(SETTINGS_SKETCH.SKETCHOUTLINE_PATH_WEIGHT_DIAGRAM
            * SETTINGS_SKETCH.PATH_WIDTH_ZOOM);
        g.noFill();
      }

      float outlineOffset2 = 0;
      if (this.isOutline)
        outlineOffset2 = 0.1f;

      this.renderFace(g);

      break;
     
      //#IF JAVA
    /*  3D Normal
     *  Nothing is being selected or edited
     */
      //#ENDIF JAVA

    case Sketch.RENDER_3D_NORMAL: {

     
      //don't review guides
      if(getParentSketch().getOnSketchPlane().guide)
      return;
     
     
      float extrudeDepth;
          if(this.getParentSketch().getOnSketchPlane() != null)
            extrudeDepth =   (this.getParentSketch().getOnSketchPlane().thickness / 2)
          / SETTINGS_SKETCH.scale;
          else
            extrudeDepth =0;

      //We might want to use a user selected chair colour in here again
      if (this.isOutline ) {

        g.fill(SETTINGS_SKETCH.RENDER_3D_NORMAL_SKETCHSHAPE_SIDE_FILL_COLOUR);
        renderSide(g, extrudeDepth * 2);

        g.noFill();
        g.stroke(SETTINGS_SKETCH.RENDER_3D_NORMAL_SKETCHOUTLINE_STROKE_COLOUR);
        g.strokeWeight(SETTINGS_SKETCH.RENDER_3D_NORMAL_SKETCHOUTLINE_STROKE_WEIGHT);

        g.pushMatrix();
        g.translate(0, 0, extrudeDepth
            + SETTINGS_SKETCH.OUTLINE_RENDER_OFFSET);
        this.renderFace(g);
        g.popMatrix();

        g.pushMatrix();
        g.translate(0, 0,
            -(extrudeDepth + SETTINGS_SKETCH.OUTLINE_RENDER_OFFSET));
        this.renderFace(g);
        g.popMatrix();
      } else {

 
        g.fill(SETTINGS_SKETCH.RENDER_3D_NORMAL_SKETCHSHAPE_FILL_COLOUR);

        g.pushMatrix();
        g.translate(0, 0, extrudeDepth);
        this.renderFace(g);
        g.popMatrix();

        g.pushMatrix();
        g.translate(0, 0, -(extrudeDepth));
        this.renderFace(g);
        g.popMatrix();
      }
    }

      break;

    case Sketch.RENDER_3D_EDITING_PLANES: {

      float extrudeDepth = 0;
     
      if(this.getParentSketch().getOnSketchPlane() != null)
        extrudeDepth = (this.getParentSketch().getOnSketchPlane().thickness / 2)
          / SETTINGS_SKETCH.scale;
      float offsetSize = 0;

      //if (!getParentSketch().getLayerSelected())
      //  g.stroke(SETTINGS_SKETCH.SKETCHSHAPE_PATH_COLOUR_UNSELECTED_LAYER);

      /*
      if ((getParentSketch().getSketchTools().getCurrentTool() == SketchTools.SELECT_TOOL || getParentSketch()
          .getSketchTools().getCurrentTool() == SketchTools.SELECT_BEZIER_TOOL)
          && !getParentSketch().screenshot)
          g.noFill();
      */

      if (this.isOutline) {

        if (this.getParentSketch().getLayerSelected()  ) {

          g.fill(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHSHAPE_SIDE_FILL_COLOUR_SELECTED);

          //only render if we are displaying in 3D
          if (getParentSketch().getRender3D())
            renderSide(g, extrudeDepth * 2);

          g.noFill();
          g.stroke(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHOUTLINE_STROKE_COLOUR_SELECTED);
          g.strokeWeight(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHOUTLINE_STROKE_WEIGHT_SELECTED);
          offsetSize = SETTINGS_SKETCH.OUTLINE_RENDER_OFFSET;

        } else {

          if (getParentSketch().getRender3D()) {
           
            g.stroke(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHOUTLINE_STROKE_COLOUR_UNSELECTED);

            //g.fill(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHSHAPE_SIDE_FILL_COLOUR_UNSELECTED);
            //renderSide(g, extrudeDepth * 2);
            //g.noStroke();
            g.noFill()
          } else {

            //g.fill(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHSHAPE_SIDE_FILL_COLOUR_UNSELECTED);
            //renderSide(g, extrudeDepth * 2);

            g.noFill();
            g.stroke(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHOUTLINE_STROKE_COLOUR_UNSELECTED);
            g.strokeWeight(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHOUTLINE_STROKE_WEIGHT_UNSELECTED);
            offsetSize = SETTINGS_SKETCH.OUTLINE_RENDER_OFFSET;
          }
        }

        //only render if we are displaying in 3D
        if (getParentSketch().getRender3D()) {

          //3D
          g.pushMatrix();
          g.translate(0, 0, extrudeDepth + offsetSize);
          this.renderFace(g);
          g.popMatrix();

          g.pushMatrix();
          g.translate(0, 0, -(extrudeDepth + offsetSize));
          this.renderFace(g);
          g.popMatrix();
        } else {
          //2D
          g.pushMatrix();
          g.translate(0, 0, offsetSize);
          this.renderFace(g);
          g.popMatrix();
         
         
        }

        //NOT OUTLINE
      } else {

        //we don't draw internal shapes on unselected layers at in 2D.
        if (!this.getParentSketch().getLayerSelected()
            && getParentSketch().getRender3D())
          return;

        if (this.getParentSketch().getLayerSelected()) {
          if (this.selected || this.highlighted) {
            g.noFill();
            g.stroke(SETTINGS_SKETCH.SKETCHSHAPE_PATH_COLOUR_SELECTED);
            g.strokeWeight(SETTINGS_SKETCH.SKETCHSHAPE_PATH_WEIGHT_SELECTED);
            offsetSize = SETTINGS_SKETCH.OUTLINE_RENDER_OFFSET * 2;
          } else {
            g.fill(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHSHAPE_FILL_COLOUR_SELECTED);
            g.stroke(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHSHAPE_STROKE_COLOUR_SELECTED);
            g.strokeWeight(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHSHAPE_STROKE_WEIGHT_SELECTED);
            offsetSize = 0;
          }
         
          if(this.highlighted){
            //g.stroke(SETTINGS_SKETCH.SKETCHSHAPE_PATH_COLOUR_SELECTED);
            //offsetSize = SETTINGS_SKETCH.OUTLINE_RENDER_OFFSET * 3;
          }
         
        } else {

          if (getParentSketch().getRender3D()) {
            g.fill(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHSHAPE_FILL_COLOUR_UNSELECTED);
            g.noStroke();
            offsetSize = 0;
          } else {
            g.fill(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHSHAPE_FILL_COLOUR_UNSELECTED);
            g.stroke(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHSHAPE_STROKE_COLOUR_UNSELECTED);
            g.strokeWeight(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHSHAPE_STROKE_WEIGHT_UNSELECTED);
            offsetSize = 0;
          }
        }

        //g.noStroke();
        //        g.stroke(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHOUTLINE_STROKE_COLOUR_UNSELECTED);
        //        g.strokeWeight(SETTINGS_SKETCH.RENDER_3D_EDITING_PLANES_SKETCHOUTLINE_STROKE_WEIGHT_UNSELECTED);

        if ((SETTINGS_SKETCH.SLICEPLACE_RENDER_VOLUME)
            && this.getParentSketch() != null
            && this.getParentSketch().getOnSketchPlane().thickness > SETTINGS.MIN_RENDER_WIDTH) {

          g.noFill();

          //only render if we are displaying in 3D
          if (getParentSketch().getRender3D()) {
            //render the face of the chairs
            g.pushMatrix();
            g.translate(0, 0, extrudeDepth + offsetSize);
            this.renderFace(g);
            g.popMatrix();

            g.pushMatrix();
            g.translate(0, 0, -(extrudeDepth + offsetSize));
            this.renderFace(g);
            g.popMatrix();
          } else {
            //2D
            g.pushMatrix();
            g.translate(0, 0, offsetSize);
            this.renderFace(g);
            g.popMatrix();
          }

        } else {
          //to thin so we only need to rend
          g.noFill();
          g.pushMatrix();
          g.translate(0, 0, offsetSize);
          this.renderFace(g);
          g.popMatrix();
        }
      }

      if (this.getParentSketch().getLayerSelected()) {

        g.pushMatrix();
        //g.translate(0, 0, extrudeDepth
        //    + (SETTINGS_SKETCH.OUTLINE_RENDER_OFFSET * 2));
        float d = extrudeDepth
            + (SETTINGS_SKETCH.OUTLINE_RENDER_OFFSET * 2);
        g.translate(0, 0,d);
       
       
        if (this.editable
            && !getParentSketch().getSketchGlobals().renderScreenshot
            && getParentSketch().getSketchTools().getCurrentTool() == SketchTools.SELECT_BEZIER_TOOL) {
          if (this.getType() == SketchShape.TYPE_SPLINE)
            renderNodesSmoothedSpline(g);
          else
            renderNodes(g);

        }

        if ((this.editable && (getParentSketch().getSketchTools()
            .getCurrentTool() == SketchTools.SELECT_TOOL || getParentSketch()
            .getSketchTools().getCurrentTool() == SketchTools.SELECT_BEZIER_TOOL ||
            getParentSketch()
            .getSketchTools().getCurrentTool() == SketchTools.DRAW_PATH_TOOL))
            && !getParentSketch().getSketchGlobals().renderScreenshot) {
          if (this.getType() == SketchShape.TYPE_SPLINE)
            renderNodesSmoothedSpline(g);
          else
            renderNodesOutline(g);

        }

        g.popMatrix();
      }

    }
      break;

    case Sketch.RENDER_3D_DIAGRAM:
      float outlineOffset = 0;
      float extrudeDepth = this.getParentSketch().getOnSketchPlane().thickness / 2;
      extrudeDepth /= SETTINGS_SKETCH.scale;

      g.fill(SETTINGS_SKETCH.SKETCHSHAPE_FILL_DIAGRAM_COLOUR);
      g.noStroke();

     
      if (this.isOutline) {
        g.noStroke();
        g.fill(SETTINGS_SKETCH.SKETCHSHAPE_FILL_DIAGRAM_COLOUR);
        renderSide(g, extrudeDepth * 2);
        g.noFill();
      }
     
     
      if (this.isOutline) {
        g.stroke(SETTINGS_SKETCH.SKETCHOUTLINE_PATH_COLOUR_DIAGRAM);
        g.strokeWeight(SETTINGS_SKETCH.SKETCHOUTLINE_PATH_WEIGHT_DIAGRAM
            * SETTINGS_SKETCH.PATH_WIDTH_ZOOM);
        g.noFill();
      }

      if (this.isOutline)
        outlineOffset = SETTINGS_SKETCH.OUTLINE_RENDER_OFFSET ;



      g.pushMatrix();
      g.translate(0, 0, extrudeDepth + outlineOffset);
      this.renderFace(g);
      g.popMatrix();

      g.pushMatrix();
      g.translate(0, 0, -(extrudeDepth + outlineOffset));
      this.renderFace(g);
      g.popMatrix();

      break;

    case Sketch.RENDER_EDIT_SELECT:
      LOGGER.info("RENDER_EDIT_SELECT"+Math.random());

      break;

    }
/*
    if (SETTINGS.DEBUG) {
      Vec2D p = this.getPos(this.debugPercent);
      if (p != null)
        g.ellipse(p.x, p.y, 25, 25);

    }
*/
  }

  private void removeBeziers() {
    for (int i = 0; i < this.l.size() - 1; i++) {
      SketchPoint curVec = this.l.get(i);
      curVec.controlPoint1 = null;
      curVec.controlPoint2 = null;

    }
  }

  protected void renderFace(PGraphics g) {
    g.beginShape();


    int loop = 0;
    if (this.getClosed() &&(this.l.size() > 0 && (this.l.get(0).containsBezier() || this.l.get(this.l.size()-1).containsBezier())))
      loop = 1;

    if (!this.getClosed())
      g.noFill();

    for (int i = 1; i < this.l.size() + loop; i++) {
      SketchPoint curVec = null;
      SketchPoint preVec = null;

      // --- last or first point ---
      if (i == 1) {
        curVec = this.l.get(0);
        g.vertex(curVec.x, curVec.y);
      }

      if (i >= 1)
        preVec = this.l.get(i - 1);

      if (i == this.l.size()) {
        curVec = this.l.get(0);
        preVec = this.l.get(this.l.size() - 1);
      } else {
        curVec = this.l.get(i);
      }

      if (curVec.containsBezier() || preVec != null
          && preVec.containsBezier()) {

        Vec2D c1 = (SketchPoint) preVec;
        Vec2D c2 = (SketchPoint) curVec;

        if (c1 == null)
          c1 = new SketchPoint(0, 0);

        if (preVec != null && preVec.containsBezier())
        {
          c1 = preVec.getControlPoint2();
        }

        if (curVec.containsBezier()) {
          c2 = curVec.getControlPoint1();
        }

        if (c1 != null && c2 != null && curVec != null)
          g.bezierVertex(c1.x, c1.y, c2.x, c2.y, curVec.x, curVec.y);

      } else {
        if (SETTINGS_SKETCH.Draw_Curves) {
          g.curveVertex(curVec.x, curVec.y);
        } else {
          g.vertex(curVec.x, curVec.y);
        }
      }

    }
    if (this.getClosed())
      g.endShape(PConstants.CLOSE);
    else
      g.endShape(PConstants.OPEN);

    if (getParentSketch().getSketchTools().getCurrentTool() == SketchTools.DRAW_PATH_TOOL
        && !this.getClosed() && this.selected && this.getLength() > 0) {

      Vec2D firstPoint = this.get(0);
      Vec2D mousePos = new Vec2D(
          getParentSketch().getSketchTools().mouseX,
          getParentSketch().getSketchTools().mouseY);

      //#IF JAVA
      if (getParentSketch().getOnSketchPlane() != null)
        mousePos = GLOBAL.uiTools.getPointOnPlane(mousePos,
            getParentSketch().getOnSketchPlane().getPlane());
      //#ENDIF JAVA
     
      if (firstPoint.distanceTo(mousePos) < SETTINGS_SKETCH.MIN_CLOSE_SHAPE_DIST)
        g.ellipse(firstPoint.x, firstPoint.y, 20, 20);

    }

  }

  private void renderNodesSmoothedSpline(PGraphics g) {
    float selectDia = (float) (SETTINGS_SKETCH.select_dia
        * (1 / getParentSketch().getSketchTools().zoom));

    if (selectDia > SETTINGS_SKETCH.select_dia * 1.5f)
      selectDia = SETTINGS_SKETCH.select_dia;

    selectDia = selectDia / 2;

    // g.fill(SETTINGS_SKETCH.sChair_controle_points_fill);
    g.noFill();
    g.stroke(SETTINGS_SKETCH.CONTROL_POINT_STROKE_COLOUR);



    g.noFill();
    g.beginShape();
    g.strokeWeight(SETTINGS_SKETCH.CONTROL_SPLINE_WEIGHT);

    //find if we are over a select point on the editble curve
    for (int i = 1; i < size() - 1; i++) {
      SketchPoint curVec = (SketchPoint) get(i);
      if (curVec.isOver) {
        g.strokeWeight(SETTINGS_SKETCH.CONTROL_SPLINE_WEIGHT * 1.5f);
        curVec.isOver = false;
      }
    }

    for (int i = 0; i < size(); i++) {
      Vec2D curVec = (Vec2D) get(i);

      g.vertex(curVec.x, curVec.y);

      //
      // selectDia);
    }
    g.endShape();
   
    g.fill(SETTINGS_SKETCH.CONTROL_POINT_FILL_COLOUR);

   
    SketchPoint startVect = (SketchPoint) get(0);
    if (startVect != null) {
      if (startVect.isOver) {
        g.rect(startVect.x - ((selectDia * 2.5f)/2), startVect.y- ((selectDia * 2.5f)/2), (selectDia * 2.5f),
            selectDia * 2.5f);
        startVect.isOver = false;
      } else
        g.rect(startVect.x - ((selectDia * 1.5f)/2), startVect.y - ((selectDia * 1.5f)/2), (selectDia * 1.5f),
            (selectDia * 1.5f));

    }

    SketchPoint endVect = (SketchPoint) get(size() - 1);
    if (endVect != null) {
      if (endVect.isOver) {
        g.rect(endVect.x- ((selectDia * 2.5f)/2), endVect.y- ((selectDia * 2.5f)/2), selectDia * 2.5f,
            selectDia * 2.5f);
        endVect.isOver = false;
      } else
        g.rect(endVect.x- ((selectDia * 1.5f)/2), endVect.y- ((selectDia * 1.5f)/2), selectDia * 1.5f,
            selectDia * 1.5f);

    }
   
   

    g.noFill();

  }

  public void renderNodes(PGraphics g) {
    if (getParentSketch().getSketchTools().renderNodesFlag) {
      g.strokeWeight(1);
      // draw selection points
      for (int i = 0; i < this.l.size(); i++) {
        SketchPoint curVec = this.l.get(i);
        //g.noFill();

        g.strokeWeight(1);
        g.stroke(SETTINGS_SKETCH.CONTROL_POINT_STROKE_COLOUR);
        // g.ellipse(curVec.x, curVec.y, 5, 5);
        g.fill(SETTINGS_SKETCH.CONTROL_POINT_STROKE_COLOUR);

        if (curVec.containsBezier()) {

          if (curVec.controlPoint1 != null) {
            g.line(curVec.x, curVec.y, curVec.controlPoint1.x,
                curVec.controlPoint1.y);
            g.ellipse(curVec.controlPoint1.x,
                curVec.controlPoint1.y, 3, 3);
          }
          if (curVec.controlPoint2 != null) {
            g.line(curVec.x, curVec.y, curVec.controlPoint2.x,
                curVec.controlPoint2.y);
            g.ellipse(curVec.controlPoint2.x,
                curVec.controlPoint2.y, 3, 3);
          }
        }

        if (curVec.isOver) {
          g.ellipse(curVec.x, curVec.y, 10, 10);
          curVec.isOver = false;
        }

      }
    }
    /*
     * //draw selection points for (int i = 0; i < this.bezierPoints.size();
     * i++) { BezierControlNode node = (BezierControlNode)
     * this.bezierPoints.get(i); BezierControlNode node =
     * (BezierControlNode) this.bezierPoints.(i);
     * g.ellipse(curVec.x,curVec.y,5,5); }
     */

  }

 
  public void renderDebug(PGraphics g) {
      g.strokeWeight(1);
      // draw selection points
      for (int i = 0; i < this.l.size(); i++) {
        SketchPoint curVec = this.l.get(i);
        //g.noFill();

        g.strokeWeight(1);
        g.stroke(SETTINGS_SKETCH.CONTROL_POINT_STROKE_COLOUR);
        // g.ellipse(curVec.x, curVec.y, 5, 5);
        g.fill(SETTINGS_SKETCH.CONTROL_POINT_STROKE_COLOUR);

        if (curVec.containsBezier()) {

          if (curVec.controlPoint1 != null) {
            g.line(curVec.x, curVec.y, curVec.controlPoint1.x,
                curVec.controlPoint1.y);
            g.ellipse(curVec.controlPoint1.x,
                curVec.controlPoint1.y, 0.1f, 0.1f);
          }
          if (curVec.controlPoint2 != null) {
            g.line(curVec.x, curVec.y, curVec.controlPoint2.x,
                curVec.controlPoint2.y);
            g.ellipse(curVec.controlPoint2.x,
                curVec.controlPoint2.y, 0.1f, 0.1f);
          }
        }

       

g.rect(curVec.x,curVec.y,0.5f,0.5f);
    }
 
  }

 
 
  private void renderNodesOutline(PGraphics g) {
   

   
   
    g.noStroke();
    float selectDia = (float) (SETTINGS_SKETCH.select_dia
        * (1 / getParentSketch().getSketchTools().zoom));

    if (selectDia > SETTINGS_SKETCH.select_dia * 1.5f)
      selectDia = SETTINGS_SKETCH.select_dia;

    selectDia = selectDia / 2;

    if (!getParentSketch().getSketchTools().mouseDown) {
      g.fill(SETTINGS_SKETCH.CONTROL_POINT_FILL_COLOUR);
      //g.noFill();
     

      //g.strokeWeight(1);
      //g.stroke(SETTINGS_SKETCH.sChair_controle_points_fill);
      for (int i = 0; i < this.l.size(); i++) {
        SketchPoint curVec = this.l.get(i);

        g.fill(SETTINGS_SKETCH.CONTROL_POINT_FILL_COLOUR);

        if (this.getSelectedNodes().contains(curVec))
          g.fill(SETTINGS_SKETCH.CONTROL_POINT_FILL_SELECTED_COLOUR);

       
        g.strokeWeight(1);
        g.stroke(SETTINGS_SKETCH.CONTROL_POINT_STROKE_COLOUR);
        //if(i == 0)
        //    g.ellipse(curVec.x, curVec.y, 100, 100);

        //g.ellipse(curVec.x, curVec.y, selectDia, selectDia);
        // g.ellipse(curVec.x, curVec.y, selectDia,
        // selectDia);
       

        if (curVec.isOver) {
        float overDia = selectDia*1.5f;
        g.rect(curVec.x-(overDia/2), curVec.y-(overDia/2), overDia, overDia);
        curVec.isOver = false;
        }else{
          g.rect(curVec.x-(selectDia/2), curVec.y-(selectDia/2), selectDia, selectDia);
        }
       
       

      }

      //g.noFill();
    }
  }

  public void renderPickBuffer(PGraphics g) {
    renderSilhouette(g);
    int fill = g.fillColor;
    float extrudeDepth = this.getParentSketch().getOnSketchPlane().thickness / 2;
    extrudeDepth /= SETTINGS_SKETCH.scale;
    renderSide(g, extrudeDepth * 2);

    /*
     * g.noStroke(); for (int i = 0; i < this.l.size(); i++) { SketchPoint curVec
     * = (SketchPoint) this.l.get(i); g.fill(getColor(i + (this.id * 100)));
     * g.pushMatrix(); g.translate(curVec.x, curVec.y); g.sphereDetail(4);
     * g.sphere(4); g.popMatrix(); }
     */

  }

  void renderSide(PGraphics g, float width) {

    float halfWidth = width / 2;
    g.noStroke();
    g.beginShape(PConstants.TRIANGLES);
    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint curPoint = null;
      SketchPoint prevPoint = null;

      curPoint = this.l.get(i);

      if (i == 0)
        prevPoint = l.get(l.size() - 1);
      else
        prevPoint = l.get(i - 1);

      //If we are drawing a bezier edge we now need to step along the bezier

      if (prevPoint.controlPoint2 != null
          || curPoint.controlPoint1 != null) {

        float dist = prevPoint.distanceTo(curPoint);
        float step = SETTINGS_SKETCH.RENDER_PIXELS_PER_TRIANGLE_BEZIER
            / dist;

        float prevX = prevPoint.x;
        float prevY = prevPoint.y;
        for (float t = 0; t <= 1; t += step) {

          float bx = functions.bezierPoint(prevPoint.x,
              prevPoint.getControlPoint2().x,
              curPoint.getControlPoint1().x, curPoint.x, t);

          float by = functions.bezierPoint(prevPoint.y,
              prevPoint.getControlPoint2().y,
              curPoint.getControlPoint1().y, curPoint.y, t);

          g.vertex(prevX, prevY, halfWidth);
          g.vertex(prevX, prevY, -halfWidth);
          g.vertex(bx, by, -halfWidth);

          g.vertex(bx, by, -halfWidth);
          g.vertex(bx, by, halfWidth);
          g.vertex(prevX, prevY, halfWidth);

          prevX = bx;
          prevY = by;
        }

      } else {

        g.vertex(prevPoint.x, prevPoint.y, halfWidth);
        g.vertex(prevPoint.x, prevPoint.y, -halfWidth);
        g.vertex(curPoint.x, curPoint.y, -halfWidth);

        g.vertex(curPoint.x, curPoint.y, -halfWidth);
        g.vertex(curPoint.x, curPoint.y, halfWidth);
        g.vertex(prevPoint.x, prevPoint.y, halfWidth);
      }

    }
    g.endShape(PConstants.CLOSE);
  }

  public void renderSilhouette(PGraphics g) {

    g.beginShape();

    for (int i = 1; i < this.l.size() + 1; i++) {
      SketchPoint curVec = null;
      SketchPoint preVec = null;

      // --- last or first point ---
      if (i == 1) {
        curVec = this.l.get(0);
        g.vertex(curVec.x, curVec.y);
      }

      if (i >= 1)
        preVec = this.l.get(i - 1);

      if (i == this.l.size()) {
        curVec = this.l.get(0);
        preVec = this.l.get(this.l.size() - 1);

      } else {
        curVec = this.l.get(i);
      }

      if (curVec.containsBezier() || preVec != null
          && preVec.containsBezier()) {

        Vec2D c1 = preVec;
        Vec2D c2 = curVec;

        if (c1 == null)
          c1 = new SketchPoint(0, 0);

        if (preVec != null && preVec.containsBezier()) {

          c1 = preVec.getControlPoint2();
        }

        if (curVec.containsBezier()) {
          c2 = curVec.getControlPoint1();
        }

        if (c1 != null && c2 != null && curVec != null)
          g.bezierVertex(c1.x, c1.y, c2.x, c2.y, curVec.x, curVec.y);

        /*
         * // --- last or first point --- if (i == this.l.size()-1 ) {
         * curVec = (SketchPoint) this.l.get(0); g.vertex(curVec.x, curVec.y);
         * }
         */

      } else {
        if (SETTINGS_SKETCH.Draw_Curves) {
          g.curveVertex(curVec.x, curVec.y);
        } else {
          g.vertex(curVec.x, curVec.y);
        }
      }

    }

    g.endShape();
  }

  public void replace(SketchShape objClone) {
    // TODO Auto-generated method stub

  }

  public void reset() {
    this.l = new ArrayList<SketchPoint>();
  }

  public void resetCachedVariables() {
    cachedLength = -1;
    cachedGetMaxX = -1;
    cachedGetMaxY = -1;
    cachedGetMinX = -1;
    cachedGetMinY = -1;
  }

  public void resetPosStep() {

    lastStepStartPos = 0;
    lastStepPercent = 0;
    lastStepLengthAlong = 0;
    lastStepAlongBezier = 0;
    lastStepMeasuredPoint = null;
    lastStepLenghtSegment = 0;
  }

  public void scale(float scale, toxi.geom.Vec3D centre) {
    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint curVec = this.l.get(i);

      LOGGER.info("-> s " + scale);

      LOGGER.info("-> 1 " + curVec.x);

      curVec.x += (curVec.x - centre.x) * scale;
      LOGGER.info("-> 2 " + curVec.x);

      LOGGER.info("");

      curVec.y += (curVec.y - centre.y) * scale;

      if (curVec.containsBezier()) {

        if (curVec.controlPoint1 != null) {
          curVec.controlPoint1.x += (curVec.controlPoint1.x - centre.x)
              * scale;
          curVec.controlPoint1.y += (curVec.controlPoint1.y - centre.y)
              * scale;
        }

        if (curVec.controlPoint2 != null) {
          curVec.controlPoint2.x += (curVec.controlPoint2.x - centre.x)
              * scale;
          curVec.controlPoint2.y += (curVec.controlPoint2.y - centre.y)
              * scale;
        }
      }

    }
  }

  public void scale(float scale) {
    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint curVec = this.l.get(i);

      curVec.scaleSelf(scale);

      if (curVec.containsBezier()) {

        if (curVec.controlPoint1 != null) {
          curVec.controlPoint1.scaleSelf(scale);
        }

        if (curVec.controlPoint2 != null) {
          curVec.controlPoint2.scaleSelf(scale);

        }
      }

    }
    resetCachedVariables();
  }

  public void rotate(float r) {
    this.rotate(r, this.getCentre());
  }

  public void rotate(float r, Vec2D centre) {

    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint curVec = this.l.get(i);

      Vec2D v = functions.rotate(curVec, centre, r);
      curVec.x = v.x;
      curVec.y = v.y;

      if (curVec.containsBezier()) {

        if (curVec.controlPoint1 != null) {
          curVec.controlPoint1 = (Vec2D) functions.rotate(
              curVec.controlPoint1, centre, r);
        }

        if (curVec.controlPoint2 != null) {
          curVec.controlPoint2 = (Vec2D) functions.rotate(
              curVec.controlPoint2, centre, r);

        }
      }

    }
    resetCachedVariables();

  }

  public void translate(Vec2D delta) {

    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint curVec = this.l.get(i);
      curVec.x += delta.x;
      curVec.y += delta.y;

      if (curVec.containsBezier()) {

        if (curVec.controlPoint1 != null) {
          curVec.controlPoint1.x += delta.x;
          curVec.controlPoint1.y += delta.y;
        }

        if (curVec.controlPoint2 != null) {
          curVec.controlPoint2.x += delta.x;
          curVec.controlPoint2.y += delta.y;
        }
      }

    }
    resetCachedVariables();
  }

  public void select() {
    this.selected = true;
  }

  public void selectNodes(float mouseX, float mouseY) {

    if (getParentSketch().getSketchTools().getCurrentTool() == SketchTools.SELECT_TOOL) {
      this.unselectNodes();
      for (int i = 0; i < this.l.size(); i++) {
        SketchPoint v = this.l.get(i);

        float selectDia = SETTINGS_SKETCH.select_dia
            * (1 / getParentSketch().getSketchGlobals().zoom);

        if (selectDia > SETTINGS_SKETCH.select_dia * 1.5f)
          selectDia = SETTINGS_SKETCH.select_dia;

        if (v.distanceTo(new SketchPoint(mouseX, mouseY)) < selectDia)
          this.getSelectedNodes().add(v);
        // System.out.println( this.selectedNodes.size() + "got one");
      } // TODO Auto-generated method stub

    }

    if (getParentSketch().getSketchTools().getCurrentTool() == SketchTools.SELECT_BEZIER_TOOL) {
      this.unselectNodes();
      for (int i = 0; i < this.l.size(); i++) {
        SketchPoint v = this.l.get(i);

        if (v.containsBezier()) {
          if (v.controlPoint1.distanceTo(new SketchPoint(mouseX,
              mouseY)) < SETTINGS_SKETCH.select_dia)
            this.getSelectedNodes().add(v.controlPoint1);

          if (v.controlPoint2.distanceTo(new SketchPoint(mouseX,
              mouseY)) < SETTINGS_SKETCH.select_dia)
            this.getSelectedNodes().add(v.controlPoint2);

        }

        if (v.distanceTo(new SketchPoint(mouseX, mouseY)) < SETTINGS_SKETCH.select_dia) {
          //System.out.println("HERE");

          if (!v.containsBezier()) {
           
            this.addBezier(v, v.copy(),v.copy());

            //choose left or right
            this.getSelectedNodes().add(v.controlPoint1);

          }

        }
        // System.out.println( this.selectedNodes.size() + "got one");
      } // TODO Auto-generated method stub

    }

  }

  public void set(int index, SketchPoint point) {
    l.set(index, point);
  }

  public void setPath(ArrayList<SketchPoint> outline) {
    this.l = outline;

  }

  public SketchPoint setSketchPointpickBuffer(int col,
      SketchPoint selectedVec, SketchShape selectedShape,
      SlicePlane selectedVecPlane, boolean isSelectedVecOnOutline) {
    // TODO Auto-generated method stub
    return null;
  }

  public void setupSpShape(spShape shape) {

    ArrayList<SketchPoint> loop = new ArrayList<SketchPoint>();
    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint v = this.l.get(i);

      SketchPoint point = new SketchPoint(v.x, v.y);

      if (v.controlPoint1 != null) {
        point.controlPoint1 = new Vec2D(v.controlPoint1.x,
            v.controlPoint1.y);
      }

      if (v.controlPoint2 != null) {
        point.controlPoint2 = new Vec2D(v.controlPoint2.x,
            v.controlPoint2.y);
      }

      loop.add(point);

    } // TODO Auto-generated method stub

    shape.addOutline(loop);
    // shape.addBeziers(bezierReturn);
    //shape.scale(SETTINGS_SKETCH.pixels_per_mm);
  }

  public void addCollisionToSpShape(spShape shape) {

    ArrayList<SketchPoint> loop = new ArrayList<SketchPoint>();
    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint v = this.l.get(i);
      SketchPoint point = new SketchPoint(v.x, v.y);

      if (v.controlPoint1 != null) {
        point.controlPoint1 = new Vec2D(v.controlPoint1.x,
            v.controlPoint1.y);
      }

      if (v.controlPoint2 != null) {
        point.controlPoint2 = new Vec2D(v.controlPoint2.x,
            v.controlPoint2.y);
      }

      loop.add(point);

    } // TODO Auto-generated method stub

    shape.addCollisionOutline(loop);

  }

  public SketchPoint setVec2DpickBuffer(int col, SketchPoint selectedVec,
      SketchShape selectedShape, SlicePlane selectedVecPlane,
      boolean isSelectedVecOnOutline) {
    // TODO Auto-generated method stub
    return null;
  }

  public int size() {
    return l.size();
  }

  public void smooth(float scale) {

    if (!WoundClockwise())
      smoothLeft(scale);
    else
      smoothRight(scale);

  }

  public void smoothLeft(float scale) {

    if (size() < 2) {
      return;
    }

    if (!getClosed()) {
      for (int i = 0; i < size(); i++) {
        if (i == 0) // is first
        {
          SketchPoint p1 = get(i);
          SketchPoint p2 = get(i + 1);
          Vec2D tangent = p2.sub(p1);
          Vec2D q1 = p1.add(tangent.scale(scale));
          p1.controlPoint1 = p1;
          p1.controlPoint2 = q1;
        } else if (i == size() - 1) //last
        {
          SketchPoint p0 = get(i - 1);
          SketchPoint p1 = get(i);
          Vec2D tangent = p1.sub(p0);
          Vec2D q0 = p1.sub(tangent.scale(scale));

          p1.controlPoint1 = q0;
          p1.controlPoint2 = p1.copy();
        } else {

          SketchPoint p0, p1, p2;
          p0 = get(i - 1);
          p1 = get(i);
          p2 = get(i + 1);

          Vec2D tangent = (p2.sub(p0)).normalize();
          Vec2D q0 = p1.sub(tangent.scale(scale).scale(
              p1.sub(p0).magnitude()));
          Vec2D q1 = p1.add(tangent.scale(scale).scale(
              p2.sub(p1).magnitude()));
          p1.controlPoint2 = q0;
          p1.controlPoint1 = q1;

        }
      }
    } else {
      for (int i = 0; i < size(); i++) {

        SketchPoint p0, p1, p2;
        if (i > 0)
          p0 = get(i - 1);
        else
          p0 = get(size() - 1);

        p1 = get(i);

        if (i < size() - 1)
          p2 = get(i + 1);
        else
          p2 = get(0);

        Vec2D tangent = (p2.sub(p0)).normalize();
        Vec2D q0 = p1.sub(tangent.scale(scale).scale(
            p1.sub(p0).magnitude()));
        Vec2D q1 = p1.add(tangent.scale(scale).scale(
            p2.sub(p1).magnitude()));
        p1.controlPoint1 = q0;
        p1.controlPoint2 = q1;

      }
    }
  }

  public void smoothRight(float scale) {

    if (size() < 2) {
      return;
    }

    if (!getClosed()) {
      for (int i = size() - 1; i >= 0; i--) {
        if (i == 0) // is first
        {
          SketchPoint p1 = get(i);
          SketchPoint p2 = get(i + 1);
          Vec2D tangent = p2.sub(p1);
          Vec2D q1 = p1.add(tangent.scale(scale));
          p1.controlPoint1 = p1;
          p1.controlPoint2 = q1;
        } else if (i == size() - 1) //last
        {
          SketchPoint p0 = get(i - 1);
          SketchPoint p1 = get(i);
          Vec2D tangent = p1.sub(p0);
          Vec2D q0 = p1.sub(tangent.scale(scale));

          p1.controlPoint1 = q0;
          p1.controlPoint2 = p1.copy();
        } else {

          SketchPoint p0, p1, p2;
          p0 = get(i + 1);
          p1 = get(i);
          p2 = get(i - 1);

          Vec2D tangent = (p2.sub(p0)).normalize();
          Vec2D q0 = p1.sub(tangent.scale(scale).scale(
              p1.sub(p0).magnitude()));
          Vec2D q1 = p1.add(tangent.scale(scale).scale(
              p2.sub(p1).magnitude()));
          p1.controlPoint2 = q0;
          p1.controlPoint1 = q1;

        }
      }
    } else {
      for (int i = size() - 1; i >= 0; i--) {

        SketchPoint p0, p1, p2;
        if (i > 0)
          p0 = get(i - 1);
        else
          p0 = get(size() - 1);

        p1 = get(i);

        if (i < size() - 1)
          p2 = get(i + 1);
        else
          p2 = get(0);

        Vec2D tangent = (p2.sub(p0)).normalize();
        Vec2D q0 = p1.sub(tangent.scale(scale).scale(
            p1.sub(p0).magnitude()));
        Vec2D q1 = p1.add(tangent.scale(scale).scale(
            p2.sub(p1).magnitude()));
        p1.controlPoint1 = q0;
        p1.controlPoint2 = q1;

      }

    }
  }

  public Element toXML() {
    Element element = new Element("SketchPath");

    element.addAttribute(new Attribute("id", String.valueOf(this.getId())));

    if (isConstructionLine())
      element.addAttribute(new Attribute("isConstructionLine", "true"));

    if (this.getClosed())
      element.addAttribute(new Attribute("closed", "true"));
    else
      element.addAttribute(new Attribute("closed", "false"));

    element.addAttribute(new Attribute("isConstructionLine", "true"));

    element.addAttribute(new Attribute("union", String.valueOf(this.union)));

    for (int i = 0; i < this.l.size(); i++) {
      SketchPoint point = this.l.get(i);
      element.appendChild(point.toXML());
    }
    return element;
  }
 
 
  public Element toXML_SVG() {
   
    Element element = new Element("path","http://www.w3.org/2000/svg");
    String svgString = "";
   
    SketchPoint firstPoint = this.l.get(0);
    svgString += "M " + firstPoint.x+ "," + firstPoint.y + " ";
   
    for (int i = 1; i < this.l.size(); i++) {
      SketchPoint point = this.l.get(i);
      SketchPoint prevPoint = this.l.get(i-1);

      if(point.containsBezier() || prevPoint.containsBezier()){
        svgString += "C " + prevPoint.getControlPoint2().x + ","+ prevPoint.getControlPoint2().y +" "+  point.getControlPoint1().x+","+point.getControlPoint1().y+ " " + point.x+ "," + point.y + " ";
      }else{
        svgString += "L " + point.x+ "," + point.y + " ";
      }
     
    }
   
    svgString += " Z";
   
    element.addAttribute(new Attribute("d", svgString));


    return element;
  }
 
 

  public void unselect() {
    this.selected = false;
  }

  public void update() {

  }

  public void woudClockwiseReset() {
    woundClockwiseReset = true;
  }

  public boolean WoundClockwise() {
    if (!woundClockwiseReset)
      return woundClockwise;

    float area = 0;
    int offset = 1;

    if (this.getClosed())
      offset = 0;

    for (int i = 0; i < this.size() - offset; i++) {
      SketchPoint p1 = this.get(i);
      SketchPoint p2;

      if (i == this.size() - 1)
        p2 = this.get(0);
      else
        p2 = this.get(i + 1);

      area += (p1.x * p2.y) - (p2.x * p1.y);
    }

    if (area < 0)
      woundClockwise = true;
    else
      woundClockwise = false;

    woundClockwiseReset = false;

    return woundClockwise;

  }

  public void reverseWinding() {
    Collections.reverse(this.l);

  }

  public void renderFlat(PGraphics g) {

    g.beginShape();

    int loop = 0;
    if (this.getClosed())
      loop = 1;

    for (int i = 1; i < this.l.size() + loop; i++) {
      SketchPoint curVec = null;
      SketchPoint preVec = null;

      // --- last or first point ---
      if (i == 1) {
        curVec = this.l.get(0);
        g.vertex(curVec.x, curVec.y);
      }

      if (i >= 1)
        preVec = this.l.get(i - 1);

      if (i == this.l.size()) {
        curVec = this.l.get(0);
        preVec = this.l.get(this.l.size() - 1);
      } else {
        curVec = this.l.get(i);
      }

      if (curVec.containsBezier() || preVec != null
          && preVec.containsBezier()) {

        Vec2D c1 = (SketchPoint) preVec;
        Vec2D c2 = (SketchPoint) curVec;

        if (c1 == null)
          c1 = new SketchPoint(0, 0);

        if (preVec != null && preVec.containsBezier()) {

          c1 = preVec.getControlPoint2();
        }

        if (curVec.containsBezier()) {
          c2 = curVec.getControlPoint1();
        }

        if (c1 != null && c2 != null && curVec != null) {
          //g.vertex(curVec.x, curVec.y);
          g.bezierVertex(c1.x, c1.y, c2.x, c2.y, curVec.x, curVec.y);

        }

      } else {

        g.vertex(curVec.x, curVec.y);

      }

    }
    if (this.getClosed())
      g.endShape(PConstants.CLOSE);
    else
      g.endShape(PConstants.OPEN);

  }

  void removeOverlapping(float tollerance) {


    List<SketchPoint> newPoints = new ArrayList();
    SketchPoint lastAddedPoint = null;

   
   
    //go though all point and add or adjust for beziers apart from last
    for (int i = 0; i < this.l.size()-1 ; i++) {
      SketchPoint curVec = this.l.get(i);

      if (lastAddedPoint == null
          || lastAddedPoint.distanceTo(curVec) > tollerance
           ) {
        newPoints.add(curVec);
        lastAddedPoint = curVec;
      }else{
       
        if(lastAddedPoint.distanceTo(curVec) < tollerance && curVec.containsBezier()){
          if(lastAddedPoint.containsBezier())
            lastAddedPoint.controlPoint2 = curVec.controlPoint2;
          else{
            lastAddedPoint.controlPoint1 = curVec.controlPoint1;
            lastAddedPoint.controlPoint2 = curVec.controlPoint2;
          }

        }
       
      }

    }
   
    if(this.l.size() > 2){
      SketchPoint firstVec = this.l.get(0);
      SketchPoint lastVec = this.l.get(this.l.size()-1);
     
      if(firstVec.distanceTo(lastVec) < tollerance  && firstVec.containsBezier() && lastVec.containsBezier())
        firstVec.controlPoint1 = lastVec.controlPoint1;
     
if(firstVec.distanceTo(lastVec) > tollerance)
  newPoints.add(lastVec);


     
    }
   
    this.l = newPoints;
 

  /*
   
    if(this.l.size() > 1){
    for (int i = 1; i < this.l.size() ; i++) {
      SketchPoint prevVec = this.l.get(i-1);

      SketchPoint curVec = this.l.get(i);

      String bez = "";
     
      if(curVec.containsBezier())
        bez = " BEZIER";
     
      LOGGER.info("DIST " + prevVec.distanceTo(curVec) + bez);

    }
   
   
   
    String bez = "";
   
    if(this.l.get(0).containsBezier())
      bez = " BEZIER";
   
    LOGGER.info("DIST " + this.l.get(0).distanceTo(this.l.get(this.l.size()-1)) + bez);
    LOGGER.info("");
    }
   
   
    */
  }

  void flattenCurves(float flatness) {

    List<SketchPoint> newPoints = new ArrayList();

    int loop = 1;
    if (this.getClosed())
      loop = 0;

    for (int i = 0; i < this.l.size() - loop; i++) {
      SketchPoint curVec = this.l.get(i);
      SketchPoint nextVec = null;
      if (i < this.l.size() - 1)
        nextVec = this.l.get(i + 1);
      else
        nextVec = this.l.get(0);

      newPoints.add(curVec);

      if (curVec.containsBezier() || nextVec.containsBezier()) {
        Vec2D bez1 = curVec;
        Vec2D bez2 = nextVec;

        if (curVec.containsBezier()) {
          bez1 = curVec.getControlPoint2();
        }

        if (nextVec.containsBezier()) {
          bez2 = nextVec.getControlPoint1();
        }

        for (float t = 0; t <= 1; t += flatness) {
          float x = functions.bezierPoint(curVec.x, bez1.x, bez2.x,
              nextVec.x, t);
          float y = functions.bezierPoint(curVec.y, bez1.y, bez2.y,
              nextVec.y, t);

          newPoints.add(new SketchPoint(x, y));
        }
      }

    }

    this.removeBeziers();
    this.l = newPoints;

  }

  public void offsetPath(float offset) {
    this.setClosed(true);
    this.flattenCurves(0.2f);
    this.removeOverlapping(0.1f);
   
    //this.add((SketchPoint)this.get(0).clone());
    SketchSpline spline = new SketchSpline(this.getParentSketch());
    spline.setCentrePath(this);
    spline.getCentrePath().setClosed(true);
    spline.joinType = SketchSpline.JOIN_BEVEL;
    spline.capType = SketchSpline.CAP_SQUARE;
    spline.setOffsetSize(offset);
    //spline.getParentSketch().getSketchGlobals().BEZIER_DETAIL_OFFSET = 0.01f;
    spline.offset();
    SketchPath sLeft = new SketchPath(this.getParentSketch(),
        spline.outineLeft);
    SketchPath sRight = new SketchPath(this.getParentSketch(),spline.outineRight);
    this.l = sLeft.l;
   
    this.removeBeziers();
    this.removeOverlapping(0.2f);

  }

  public void simplifyDouglasPeucker(float epsilon) {
    this.l = this.simplifyDouglasPeucker(
        functions.getRange(this.l, 0, this.l.size() - 1), epsilon);
  }

  public List<SketchPoint> simplifyDouglasPeucker(List<SketchPoint> points,
      float epsilon) {
    if (points.size() <= 1)
      return new ArrayList();

    //function DouglasPeucker(PointList[], epsilon)
    //Find the point with the maximum distance
    double dmax = 0;
    int index = 0;

    for (int i = 1; i < points.size() - 2; i++) {

      double d = PerpendicularDistance(points.get(0).copy(),
          points.get(points.size() - 1).copy(), points.get(i).copy());

      if (d > dmax) {
        index = i;
        dmax = d;
      }
    }
    ArrayList<SketchPoint> resultList = new ArrayList();
    //If max distance is greater than epsilon, recursively simplify
    if (dmax >= epsilon) {
      //Recursive call

      List<SketchPoint> recResults1 = simplifyDouglasPeucker(
          functions.getRange(points, 0, index), epsilon);
      List<SketchPoint> recResults2 = simplifyDouglasPeucker(
          functions.getRange(points, index, points.size() - 1),
          epsilon);

      resultList.addAll(functions.getRange(recResults1, 0,
          recResults1.size() - 2));
      resultList.addAll(recResults2);

      // Build the result list
      //  ResultList[] = {recResults1[1...end-1] recResults2[1...end]}
    } else {
      resultList.add((SketchPoint) points.get(0).clone());
      resultList.add((SketchPoint) points.get(points.size() - 1).clone());
    }

    //Return the result
    return resultList;

  }

  public static double PerpendicularDistance(Vec2D Point1, Vec2D Point2,
      Vec2D Point) {

    //LOGGER.info("PerpendicularDistance");
    //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)|   *Area of triangle
    //Base = v((x1-x2)²+(x1-x2)²)                               *Base of Triangle*
    //Area = .5*Base*H                                          *Solve for height
    //Height = Area/.5/Base

    double area = Math
        .abs(.5 * (Point1.x * Point2.y + Point2.x * Point.y + Point.x
            * Point1.y - Point2.x * Point1.y - Point.x * Point2.y - Point1.x
            * Point.y));
    double bottom = Math.sqrt(Math.pow(Point1.x - Point2.x, 2)
        + Math.pow(Point1.y - Point2.y, 2));
    double height = area / bottom * 2;

    return height;

    //Another option
    //Double A = Point.X - Point1.X;
    //Double B = Point.Y - Point1.Y;
    //Double C = Point2.X - Point1.X;
    //Double D = Point2.Y - Point1.Y;

    //Double dot = A * C + B * D;
    //Double len_sq = C * C + D * D;
    //Double param = dot / len_sq;

    //Double xx, yy;

    //if (param < 0)
    //{
    //    xx = Point1.X;
    //    yy = Point1.Y;
    //}
    //else if (param > 1)
    //{
    //    xx = Point2.X;
    //    yy = Point2.Y;
    //}
    //else
    //{
    //    xx = Point1.X + param * C;
    //    yy = Point1.Y + param * D;
    //}

    //Double d = DistanceBetweenOn2DPlane(Point, new Point(xx, yy));
  }
@Override
public
void setClosed(boolean _closed){
  super.setClosed(_closed);
 
 
  //if closed always make sure we don't start and end on the same point
  if(_closed){
   
    if(this.l.size() > 2){
      SketchPoint firstVec = this.l.get(0);
      SketchPoint lastVec = this.l.get(this.l.size()-1);
      if(firstVec.distanceTo(lastVec) < PApplet.EPSILON){
      if(lastVec.containsBezier())
        firstVec.controlPoint1 = lastVec.controlPoint1;
     
      //remove the last element
      this.l.remove(this.l.size()-1);
      }
    }
  }
}
}
TOP

Related Classes of cc.sketchchair.sketch.SketchPath

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.