/******************************************************************************* * 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); } } } } }