From 2092acf520e3f346f15e42c76c2f616e7d094c97 Mon Sep 17 00:00:00 2001 From: Jonathan Duran Date: Thu, 9 Feb 2012 12:10:37 -0800 Subject: Squashed commit of the following: commit ce81a3f4387d80f9ac406e73b843fb5dbe9cf432 Merge: e78f431 fba39db Author: Valerio Virgillito Date: Thu Feb 9 11:57:35 2012 -0800 Merge pull request #26 from pushkarjoshi/pentool Pentool commit e78f4312c194d9e8188075cf1fe87e4be22b24e4 Author: Valerio Virgillito Date: Thu Feb 9 11:56:40 2012 -0800 adding oneway to the 3d bindings to fix a bug where the stage was getting selected. Signed-off-by: Valerio Virgillito commit fba39dbb3bd64eddf6162fbf57232089e446fb06 Author: Pushkar Joshi Date: Thu Feb 9 10:50:05 2012 -0800 removing shaders commit fa700027b541ec8f37c55f4fe17da5f78759ebd5 Author: Pushkar Joshi Date: Wed Feb 8 16:06:37 2012 -0800 fill all paths even if they are open commit 18243deb66ab14a014756bfb0be1a52648c7771a Merge: 802e92e 0537f8f Author: Pushkar Joshi Date: Wed Feb 8 15:42:40 2012 -0800 Merge branch 'master' into pentool Conflicts: js/helper-classes/RDGE/GLWorld.js js/helper-classes/RDGE/MaterialsLibrary.js commit 802e92eb70b00849dadacf2c6590d27edbe65d99 Author: Pushkar Joshi Date: Wed Feb 8 15:39:47 2012 -0800 bug fixes for better anchor point rotation and removing snapping on hover and mouse down commit 9b6b228524f14bf65ba60aaf3d0993c8ec5bff2d Author: Pushkar Joshi Date: Tue Feb 7 15:22:11 2012 -0800 some minor bug fixes and code cleanup commit 4bbe42e6d01fd0f81d13357a75b40eae9925dda3 Merge: e7aa17a 8950b34 Author: Pushkar Joshi Date: Tue Feb 7 07:21:27 2012 -0800 Merge branch 'master' into pentool commit e7aa17a9b472640355e95c54841399f6203050d4 Author: Pushkar Joshi Date: Tue Feb 7 07:20:28 2012 -0800 don't use colorToHex function because it only works for 3D color (needs a fix to colorToHex) commit acc500d1f1c76f4e7c93ae1cfea8d925ca95e7b9 Merge: 4d4de64 4222db9 Author: Pushkar Joshi Date: Thu Feb 2 11:28:45 2012 -0800 Merge branch 'working' of c:/Code/github/emueller/ninja-internal/ into pentool commit 4d4de64472603426a73b26cc98ba8206190949b8 Merge: 0e87c02 5233508 Author: Pushkar Joshi Date: Thu Feb 2 11:19:30 2012 -0800 Merge branch 'master' into pentool commit 4222db97e353fb65fab787ba5927d16d9fa4e1f7 Author: hwc487 Date: Wed Feb 1 16:18:26 2012 -0800 Removed a console log and set the Plasma material to animating. commit 30bc466a0ac80f8303e223c19704b90457293cdc Author: hwc487 Date: Wed Feb 1 15:57:20 2012 -0800 Fixed plane orientations for view orientations other than front. commit 11db5a63bda57c630eaf1d8baded9b79ca7ac1f2 Author: hwc487 Date: Wed Feb 1 15:11:21 2012 -0800 listen for the appMouseUp event in the Pan tool commit 823945a2bcb42bbf9c6a1cd0ef723b8f415e557f Author: hwc487 Date: Wed Feb 1 14:39:46 2012 -0800 factor in the zoom factor when drawing the stage compass. commit 0e87c02e74e08c7bf156373b0d2459563e17ecd6 Author: Pushkar Joshi Date: Wed Feb 1 14:38:15 2012 -0800 make GLAnchorPoint functions as part of its prototype commit 5a288b9d3f8e6690149978d9d0e7bea98cc81312 Author: hwc487 Date: Wed Feb 1 14:09:24 2012 -0800 Fix for a marquee zoom bug. commit 715f95cacead0025a57823e4cefa22e618f15df0 Author: hwc487 Date: Wed Feb 1 14:00:06 2012 -0800 mouse wheel for zoom tool commit 62d38a080b2918a0284c854e9bf882bfeeea1e0b Author: Pushkar Joshi Date: Wed Feb 1 13:21:11 2012 -0800 avoid overriding GlGeomObj translate method commit 799369e153baf92eb10f26e91a1ab664900da8ed Merge: b2ce8b8 c0f4374 Author: hwc487 Date: Wed Feb 1 13:06:17 2012 -0800 Merge branch 'ToolFixes' of github.com:mqg734/ninja-internal into working commit b2ce8b819cc85a558d862c04965b7e65a6ce8640 Author: hwc487 Date: Wed Feb 1 13:05:32 2012 -0800 changes to allow minimal rendering ofnon-animated materials. commit ada488781ff815a827e7f5903f2c55773e3be0f3 Author: Pushkar Joshi Date: Wed Feb 1 12:16:11 2012 -0800 when selecting anchor points, first check if the selected anchor position is close enough commit 107f79288ed87a282dd52075640297cc02bdf318 Author: Pushkar Joshi Date: Wed Feb 1 12:00:44 2012 -0800 performance improvement: add most of the GLSubpath functions to its prototype commit c00d5d3072e487be200559f692ce4399222d5fa5 Author: Pushkar Joshi Date: Tue Jan 31 14:25:05 2012 -0800 handle the case of proper redraw when the alt key is held down even after mouse up commit c006b3e75d5e23da63687a04cd30bf56a3a8a80d Merge: f0e3fa6 1d8af9f Author: Pushkar Joshi Date: Tue Jan 31 12:07:43 2012 -0800 Merge branch 'master' into pentool commit f0e3fa691b3c042c9fc49a7a0cde8ddf8100c195 Author: Pushkar Joshi Date: Tue Jan 31 12:05:15 2012 -0800 display the anchor point to be selected on mouse hover commit aa1b4b78d9e1b9cc15529dbf7196b7ac8a88e260 Merge: 0b8d8b2 6066b9b Author: hwc487 Date: Tue Jan 31 10:46:48 2012 -0800 Merge branch 'ToolFixes' of github.com:mqg734/ninja-internal into working Conflicts: js/stage/stage.reel/stage.js commit 0b8d8b2eb595b64ef53440b949f3c5ec891daf8a Merge: 8e43a46 e4837ed Author: hwc487 Date: Tue Jan 31 09:22:56 2012 -0800 Merge branch 'master' of github.com:Motorola-Mobility/ninja-internal into working commit 8e43a46e3d79323fe06dc7771bc611a2c3c85c5c Author: hwc487 Date: Mon Jan 30 16:15:12 2012 -0800 Renderer startup handling of non-animated materials. Changed zoom from the document bar to keep the location center of the viewable portion of the document fixed. commit c41d2c2b749b67921f243fb7594ce0cdb1ccce36 Merge: 86a801c f129a23 Author: hwc487 Date: Mon Jan 30 16:09:00 2012 -0800 Merge branch 'staging' of github.com:Motorola-Mobility/ninja-internal into working commit 972e0ce4df7b332601ad006ca8b7dd9e189a59ef Author: Pushkar Joshi Date: Mon Jan 30 14:38:48 2012 -0800 do the fill before the stroke for the path, so the stroke width renders acc. to user specification commit da7ad067b146200847b543faf288844221dff928 Author: Pushkar Joshi Date: Mon Jan 30 14:30:31 2012 -0800 missed a couple of pen tool property files on last checkin commit 76abbaafb0d90bb1dc9c63a5a5a78ab95bb00420 Author: Pushkar Joshi Date: Mon Jan 30 13:56:33 2012 -0800 Merge pushkar branch on gerritt with github version commit 86a801c057fc3b0580d6130be5740c2ee503444f Author: hwc487 Date: Fri Jan 27 15:52:36 2012 -0800 updated from old repo Signed-off-by: Jonathan Duran --- js/helper-classes/RDGE/GLSubpath.js | 2317 ++++++++++++++--------------------- 1 file changed, 887 insertions(+), 1430 deletions(-) (limited to 'js/helper-classes/RDGE/GLSubpath.js') diff --git a/js/helper-classes/RDGE/GLSubpath.js b/js/helper-classes/RDGE/GLSubpath.js index 55b7e49a..3200cf59 100644 --- a/js/helper-classes/RDGE/GLSubpath.js +++ b/js/helper-classes/RDGE/GLSubpath.js @@ -4,7 +4,7 @@ No rights, expressed or implied, whatsoever to this software are provided by Mot (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. */ -// Todo: This shoudl be converted to a module +// Todo: This entire class should be converted to a module var VecUtils = require("js/helper-classes/3D/vec-utils").VecUtils; @@ -35,6 +35,7 @@ function SegmentIntersections(){ // representation a sequence of cubic bezier curves. // Derived from class GLGeomObj /////////////////////////////////////////////////////////////////////// + function GLSubpath() { /////////////////////////////////////////////////// // Instance variables @@ -50,14 +51,6 @@ function GLSubpath() { this._UnprojectedAnchors = []; - //offset path samples and the points on the input path they map to - this._offsetPointsLeft = []; - this._offsetPointsRight = []; - - //triangles determined by the offset points - this._offsetTrianglesLeft = []; - this._offsetTrianglesRight = []; - //initially set the _dirty bit so we will construct samples this._dirty = true; @@ -88,7 +81,6 @@ function GLSubpath() { this._planeMatInv = null; this._planeCenter = null; - // initialize the inherited members this.inheritedFrom = GLGeomObj; this.inheritedFrom(); @@ -105,681 +97,545 @@ function GLSubpath() { this._DEFAULT_STROKE_WIDTH = 20; //use only if stroke width not specified this._MAX_OFFSET_ANGLE = 10; //max angle (in degrees) between consecutive vectors from curve to offset path - ///////////////////////////////////////////////////////// - // Property Accessors/Setters - ///////////////////////////////////////////////////////// - this.setWorld = function (world) { this._world = world; } - this.getWorld = function () { return this._world; } - this.makeDirty = function () {this._dirty = true;} - this.geomType = function () { return this.GEOM_TYPE_CUBIC_BEZIER; } - this.setDrawingTool = function (tool) {this._drawingTool = tool;} - this.getDrawingTool = function () {return this._drawingTool;} - this.setPlaneMatrix = function(planeMat){this._planeMat = planeMat;} - this.setPlaneMatrixInverse = function(planeMatInv){this._planeMatInv = planeMatInv;} - this.setPlaneCenter = function(pc){this._planeCenter = pc;} - - this.getCanvasX = function(){return this._canvasX;} - this.getCanvasY = function(){return this._canvasY;} - this.setCanvasX = function(cx){this._canvasX=cx;} - this.setCanvasY = function(cy){this._canvasY=cy;} - - this.getIsClosed = function () {return this._isClosed;} - this.setIsClosed = function (isClosed) { - if (this._isClosed !== isClosed) { - this._isClosed = isClosed; - this._dirty = true; - } - } - this.getNumAnchors = function () { return this._Anchors.length; } - this.getAnchor = function (index) { return this._Anchors[index]; } - this.addAnchor = function (anchorPt) { - this._Anchors.push(anchorPt); - this._selectedAnchorIndex = this._Anchors.length-1; - this._dirty = true; - } + // (current GLGeomObj complains if buildBuffers/render is added to GLSubpath prototype) + //buildBuffers + // Build the stroke vertices, normals, textures and colors + // Add that array data to the GPU using OpenGL data binding + this.buildBuffers = function () { + return; //no need to do anything for now + }//buildBuffers() - this.insertAnchor = function(anchorPt, index){ - this._Anchors.splice(index, 0, anchorPt); - } + //render + // specify how to render the subpath in Canvas2D + this.render = function () { + // get the world + var world = this.getWorld(); + if (!world) throw( "null world in subpath render" ); - //remove and return anchor at specified index, return null on error - this.removeAnchor = function (index) { - var retAnchor = null; - if (index < this._Anchors.length) { - retAnchor = this._Anchors.splice(index, 1); - this._dirty = true; - } - //deselect the removed anchor if necessary - if (this._selectedAnchorIndex === index){ - this._selectedAnchorIndex = -1; - } - return retAnchor; - } + // get the context + var ctx = world.get2DContext(); + if (!ctx) throw ("null context in subpath render") - //remove all the anchor points - this.clearAllAnchors = function () { - this._Anchors = []; - this._isClosed = false; - this._dirty = true; - } + var numAnchors = this.getNumAnchors(); + if (numAnchors === 0) + return; //nothing to do for empty paths - this.insertAnchorAtParameter = function(index, param) { - if (index+1 >= this._Anchors.length && !this._isClosed) { - return; - } - //insert an anchor after the specified index using the parameter, using De Casteljau subdivision - var nextIndex = (index+1)%this._Anchors.length; + ctx.save(); - //build the De Casteljau points - var P0P1 = VecUtils.vecInterpolate(3, this._Anchors[index].getPos(), this._Anchors[index].getNext(), param); - var P1P2 = VecUtils.vecInterpolate(3, this._Anchors[index].getNext(), this._Anchors[nextIndex].getPrev(), param); - var P2P3 = VecUtils.vecInterpolate(3, this._Anchors[nextIndex].getPrev(), this._Anchors[nextIndex].getPos(), param); + this.createSamples(); //dirty bit checked in this function...will generate a polyline representation + var bboxMin = this.getBBoxMin(); + var bboxMax = this.getBBoxMax(); + var bboxWidth = bboxMax[0] - bboxMin[0]; + var bboxHeight = bboxMax[1] - bboxMin[1]; + var bboxMid = Vector.create([0.5 * (bboxMax[0] + bboxMin[0]), 0.5 * (bboxMax[1] + bboxMin[1]), 0.5 * (bboxMax[2] + bboxMin[2])]); - var P0P1P2 = VecUtils.vecInterpolate(3, P0P1, P1P2, param); - var P1P2P3 = VecUtils.vecInterpolate(3, P1P2, P2P3, param); - var anchorPos = VecUtils.vecInterpolate(3, P0P1P2, P1P2P3, param); + ctx.clearRect(0, 0, bboxWidth, bboxHeight); - //update the next of the anchor at index and prev of anchor at nextIndex - var isPrevCoincident = false; - var isNextCoincident = false; - if (VecUtils.vecDist( 3, P0P1, this._Anchors[index].getNext()) < this._SAMPLING_EPSILON) { - //no change to the next point - isPrevCoincident = true; - } else { - this._Anchors[index].setNextPos(P0P1[0], P0P1[1], P0P1[2]); + ctx.lineWidth = this._strokeWidth; + ctx.strokeStyle = "black"; + if (this._strokeColor) + ctx.strokeStyle = MathUtils.colorToHex( this._strokeColor ); + ctx.fillStyle = "white"; + if (this._fillColor){ + //ctx.fillStyle = MathUtils.colorToHex( this._fillColor ); + var fillColorStr = "rgba("+parseInt(255*this._fillColor[0])+","+parseInt(255*this._fillColor[1])+","+parseInt(255*this._fillColor[2])+","+this._fillColor[3]+")"; + ctx.fillStyle = fillColorStr; } + var lineCap = ['butt','round','square']; + ctx.lineCap = lineCap[1]; + ctx.beginPath(); - if (VecUtils.vecDist( 3, P2P3, this._Anchors[nextIndex].getPrev()) < this._SAMPLING_EPSILON) { - //no change to the prev point - isNextCoincident = true; - } else { - this._Anchors[nextIndex].setPrevPos(P2P3[0], P2P3[1], P2P3[2]); + /* + commenting this out for now because of Chrome bug where coincident endpoints of bezier curve cause the curve to not be rendered + var prevAnchor = this.getAnchor(0); + ctx.moveTo(prevAnchor.getPosX()-bboxMin[0],prevAnchor.getPosY()-bboxMin[1]); + for (var i = 1; i < numAnchors; i++) { + var currAnchor = this.getAnchor(i); + ctx.bezierCurveTo(prevAnchor.getNextX()-bboxMin[0],prevAnchor.getNextY()-bboxMin[1], currAnchor.getPrevX()-bboxMin[0], currAnchor.getPrevY()-bboxMin[1], currAnchor.getPosX()-bboxMin[0], currAnchor.getPosY()-bboxMin[1]); + prevAnchor = currAnchor; + } + if (this._isClosed === true) { + var currAnchor = this.getAnchor(0); + ctx.bezierCurveTo(prevAnchor.getNextX()-bboxMin[0],prevAnchor.getNextY()-bboxMin[1], currAnchor.getPrevX()-bboxMin[0], currAnchor.getPrevY()-bboxMin[1], currAnchor.getPosX()-bboxMin[0], currAnchor.getPosY()-bboxMin[1]); + prevAnchor = currAnchor; + ctx.fill(); } + */ - //create a new anchor point - var newAnchor = new GLAnchorPoint(); - if (isPrevCoincident && isNextCoincident){ - anchorPos[0]=P1P2[0];anchorPos[1]=P1P2[1];anchorPos[2]=P1P2[2]; - newAnchor.setPos(anchorPos[0],anchorPos[1],anchorPos[2]); - newAnchor.setPrevPos(anchorPos[0],anchorPos[1],anchorPos[2]); - newAnchor.setNextPos(anchorPos[0],anchorPos[1],anchorPos[2]); - } else { - newAnchor.setPrevPos(P0P1P2[0], P0P1P2[1], P0P1P2[2]); - newAnchor.setNextPos(P1P2P3[0], P1P2P3[1], P1P2P3[2]); - newAnchor.setPos(anchorPos[0], anchorPos[1], anchorPos[2]); + var numPoints = this._samples.length/3; + ctx.moveTo(this._samples[0]-bboxMin[0],this._samples[1]-bboxMin[1]); + for (var i=0;i bboxMax[d]){ - bboxMax[d] = controlPts[i][d]; - } - } - } - //check whether the bbox of the control points contains the point within the specified radius - for (var d=0;d<3;d++){ - if (point[d] < (bboxMin[d]-radius)){ - return null; - } - if (point[d] > (bboxMax[d]+radius)){ - return null; - } - } +GLSubpath.prototype.insertAnchor = function(anchorPt, index){ + this._Anchors.splice(index, 0, anchorPt); +} - //check if the curve is already flat, and if so, check the distance from the segment C0C3 to the point - //measure distance of C1 and C2 to segment C0-C3 - var distC1 = MathUtils.distPointToSegment(controlPts[1], controlPts[0], controlPts[3]); - var distC2 = MathUtils.distPointToSegment(controlPts[2], controlPts[0], controlPts[3]); - var maxDist = Math.max(distC1, distC2); - var threshold = this._SAMPLING_EPSILON; //this should be set outside this function //TODO - if (maxDist < threshold) { //if the curve is flat - var distP = MathUtils.distPointToSegment(point, controlPts[0], controlPts[3]); //TODO we may need to neglect cases where the non-perpendicular distance is used... - if (distP>radius) - return null; - else { - var param = MathUtils.paramPointProjectionOnSegment(point, controlPts[0], controlPts[3]); //TODO this function is already called in distPointToSegment...optimize by removing redundant call - //var param = VecUtils.vecDist(3, point, controlPts[0])/VecUtils.vecDist(3, controlPts[3], controlPts[0]); - if (param<0) - param=0; - if (param>1) - param=1; - - return beginParam + (endParam-beginParam)*param; - } - } +//remove and return anchor at specified index, return null on error +GLSubpath.prototype.removeAnchor = function (index) { + var retAnchor = null; + if (index < this._Anchors.length) { + retAnchor = this._Anchors.splice(index, 1); + this._dirty = true; + } + //deselect the removed anchor + this._selectedAnchorIndex = -1; + return retAnchor; +} - //subdivide this curve using De Casteljau interpolation - var C0_ = VecUtils.vecInterpolate(3, controlPts[0], controlPts[1], 0.5); - var C1_ = VecUtils.vecInterpolate(3, controlPts[1], controlPts[2], 0.5); - var C2_ = VecUtils.vecInterpolate(3, controlPts[2], controlPts[3], 0.5); +GLSubpath.prototype.deselectAnchorPoint = function(){ + this._selectedAnchorIndex = -1; +} - var C0__ = VecUtils.vecInterpolate(3, C0_, C1_, 0.5); - var C1__ = VecUtils.vecInterpolate(3, C1_, C2_, 0.5); +GLSubpath.prototype.reversePath = function() { + var revAnchors = []; + var numAnchors = this._Anchors.length; + var lastIndex = numAnchors-1; + if (lastIndex<0){ + return; //cannot reverse empty path + } + for (var i=lastIndex;i>=0;i--) { + var newAnchor = new GLAnchorPoint(); + var oldAnchor = this._Anchors[i]; + newAnchor.setPos(oldAnchor.getPosX(),oldAnchor.getPosY(),oldAnchor.getPosZ()); + newAnchor.setPrevPos(oldAnchor.getNextX(),oldAnchor.getNextY(),oldAnchor.getNextZ()); + newAnchor.setNextPos(oldAnchor.getPrevX(),oldAnchor.getPrevY(),oldAnchor.getPrevZ()); + revAnchors.push(newAnchor); + } + if (this._selectedAnchorIndex >= 0){ + this._selectedAnchorIndex = (numAnchors-1) - this._selectedAnchorIndex; + } + this._Anchors = revAnchors; + this._dirty=true; +} - var C0___ = VecUtils.vecInterpolate(3, C0__, C1__, 0.5); +//remove all the anchor points +GLSubpath.prototype.clearAllAnchors = function () { + this._Anchors = []; + this._isClosed = false; + this._dirty = true; +} - //recursively sample the first half of the curve - var midParam = (endParam+beginParam)*0.5; - var param1 = this._checkIntersection(Vector.create([controlPts[0],C0_,C0__,C0___]), beginParam, midParam, point, radius); - if (param1!==null){ - return param1; - } +GLSubpath.prototype.insertAnchorAtParameter = function(index, param) { + if (index+1 >= this._Anchors.length && !this._isClosed) { + return; + } + //insert an anchor after the specified index using the parameter, using De Casteljau subdivision + var nextIndex = (index+1)%this._Anchors.length; + + //build the De Casteljau points + var P0P1 = VecUtils.vecInterpolate(3, this._Anchors[index].getPos(), this._Anchors[index].getNext(), param); + var P1P2 = VecUtils.vecInterpolate(3, this._Anchors[index].getNext(), this._Anchors[nextIndex].getPrev(), param); + var P2P3 = VecUtils.vecInterpolate(3, this._Anchors[nextIndex].getPrev(), this._Anchors[nextIndex].getPos(), param); + + var P0P1P2 = VecUtils.vecInterpolate(3, P0P1, P1P2, param); + var P1P2P3 = VecUtils.vecInterpolate(3, P1P2, P2P3, param); + var anchorPos = VecUtils.vecInterpolate(3, P0P1P2, P1P2P3, param); + + + //update the next of the anchor at index and prev of anchor at nextIndex + var isPrevCoincident = false; + var isNextCoincident = false; + if (VecUtils.vecDist( 3, P0P1, this._Anchors[index].getNext()) < this._SAMPLING_EPSILON) { + //no change to the next point + isPrevCoincident = true; + } else { + this._Anchors[index].setNextPos(P0P1[0], P0P1[1], P0P1[2]); + } - //recursively sample the second half of the curve - var param2 = this._checkIntersection(Vector.create([C0___,C1__,C2_,controlPts[3]]), midParam, endParam, point, radius); - if (param2!==null){ - return param2; - } + if (VecUtils.vecDist( 3, P2P3, this._Anchors[nextIndex].getPrev()) < this._SAMPLING_EPSILON) { + //no change to the prev point + isNextCoincident = true; + } else { + this._Anchors[nextIndex].setPrevPos(P2P3[0], P2P3[1], P2P3[2]); + } - //no intersection, so return null - return null; + //create a new anchor point + var newAnchor = new GLAnchorPoint(); + + if (isPrevCoincident && isNextCoincident){ + anchorPos[0]=P1P2[0];anchorPos[1]=P1P2[1];anchorPos[2]=P1P2[2]; + newAnchor.setPos(anchorPos[0],anchorPos[1],anchorPos[2]); + newAnchor.setPrevPos(anchorPos[0],anchorPos[1],anchorPos[2]); + newAnchor.setNextPos(anchorPos[0],anchorPos[1],anchorPos[2]); + } else { + newAnchor.setPrevPos(P0P1P2[0], P0P1P2[1], P0P1P2[2]); + newAnchor.setNextPos(P1P2P3[0], P1P2P3[1], P1P2P3[2]); + newAnchor.setPos(anchorPos[0], anchorPos[1], anchorPos[2]); } - //whether the point lies within the bbox given by the four control points - this._isWithinBoundingBox = function(point, ctrlPts, radius) { - var bboxMin = Vector.create([Infinity, Infinity, Infinity]); - var bboxMax = Vector.create([-Infinity,-Infinity,-Infinity]); - for (var i=0;i bboxMax[d]){ - bboxMax[d] = ctrlPts[i][d]; - } - } + //insert the new anchor point at the correct index and set it as the selected anchor + this._Anchors.splice(nextIndex, 0, newAnchor); + this._selectedAnchorIndex = nextIndex; + this._dirty = true; +} + +GLSubpath.prototype._checkIntersectionWithSamples = function(startIndex, endIndex, point, radius){ + //check whether the point is within the radius distance from the curve represented as a polyline in _samples + //return the parametric distance along the curve if there is an intersection, else return null + //will assume that the BBox test is performed outside this function + if (endIndex (bboxMax[d]+radius)){ - return false; + if (controlPts[i][d] > bboxMax[d]){ + bboxMax[d] = controlPts[i][d]; } } - return true; } - - //pick the path point closest to the specified location, return null if some anchor point (or its handles) is within radius, else return the parameter distance - this.pickPath = function (pickX, pickY, pickZ, radius) { - var numAnchors = this._Anchors.length; - var selAnchorIndex = -1; - var retCode = this.SEL_NONE; - var radSq = radius * radius; - var minDistance = Infinity; - for (var i = 0; i < numAnchors; i++) { - var distSq = this._Anchors[i].getDistanceSq(pickX, pickY, pickZ); - //check the anchor point - if (distSq < minDistance && distSq < radSq) { - selAnchorIndex = i; - minDistance = distSq; - retCode = retCode | this.SEL_ANCHOR; - } - }//for every anchor i - - //check the prev and next of the selected anchor if the above did not register a hit - if (this._selectedAnchorIndex>=0 && selAnchorIndex === -1) { - var distSq = this._Anchors[this._selectedAnchorIndex].getPrevDistanceSq(pickX, pickY, pickZ); - if (distSq < minDistance && distSq < radSq){ - selAnchorIndex = this._selectedAnchorIndex; - minDistance = distSq; - retCode = retCode | this.SEL_PREV; - } else { - //check the next for this anchor point - distSq = this._Anchors[this._selectedAnchorIndex].getNextDistanceSq(pickX, pickY, pickZ); - if (distSq (bboxMax[d]+radius)){ + return null; } - this.setIsClosed(subpath.getIsClosed()); - this.setStrokeWidth(subpath.getStrokeWidth()); } - this.translate = function (tx, ty, tz) { - for (var i=0;iradius) + return null; + else { + var param = MathUtils.paramPointProjectionOnSegment(point, controlPts[0], controlPts[3]); //TODO this function is already called in distPointToSegment...optimize by removing redundant call + //var param = VecUtils.vecDist(3, point, controlPts[0])/VecUtils.vecDist(3, controlPts[3], controlPts[0]); + if (param<0) + param=0; + if (param>1) + param=1; + + return beginParam + (endParam-beginParam)*param; } - this._dirty = true; } - // _cleanupOffsetSamples (offSamples) - // removes retrograde segments of the offset path samples - // returns the cleaned up offset samples - this._cleanupOffsetSamples = function (offSamples, width) { - var retOffSamples = []; - var numSamples = offSamples.length; - var keepOffSample = []; - for (var i = 0; i < numSamples; i++) { - keepOffSample.push(true); - } - + //subdivide this curve using De Casteljau interpolation + var C0_ = VecUtils.vecInterpolate(3, controlPts[0], controlPts[1], 0.5); + var C1_ = VecUtils.vecInterpolate(3, controlPts[1], controlPts[2], 0.5); + var C2_ = VecUtils.vecInterpolate(3, controlPts[2], controlPts[3], 0.5); - //NOTE: this is very slow O(n^2) for now...testing only - //remove any sample that's less than 'width' far from any other boundary segment - for (var i = 0; i < numSamples; i++) { - //build the current offset point - var O = Vector.create([offSamples[i].Pos[0], offSamples[i].Pos[1], offSamples[i].Pos[2]]); - - //iterate over all the path segments - var numPoints = this._samples.length / 3; - for (var j = 0; j < numPoints - 1; j++) { - var C0 = Vector.create([this._samples[3 * j], this._samples[3 * j + 1], this._samples[3 * j + 2]]); //segment startpoint - var C1 = Vector.create([this._samples[3 * (j + 1)], this._samples[3 * (j + 1) + 1], this._samples[3 * (j + 1) + 2]]); //segment endpoint - var distToSeg = MathUtils.distPointToSegment(O, C0, C1); - if (width - distToSeg > 1) { //if the distance is smaller than the width - keepOffSample[i] = false; - break; - } //if (width - distToC > 1 ) { //if the distance is substantially smaller than the width - } //for (var j=0;jstartIndex+1 intersects the segment from endIndex-1->endIndex - var seg0Start = Vector.create([offSamples[startIndex].Pos[0],offSamples[startIndex].Pos[1],offSamples[startIndex].Pos[2]]); - var seg0End = Vector.create([offSamples[startIndex + 1].Pos[0],offSamples[startIndex + 1].Pos[1],offSamples[startIndex + 1].Pos[2]]); - var seg1Start = Vector.create([offSamples[endIndex - 1].Pos[0],offSamples[endIndex - 1].Pos[1],offSamples[endIndex - 1].Pos[2]]); - var seg1End = Vector.create([offSamples[endIndex].Pos[0],offSamples[endIndex].Pos[1],offSamples[endIndex].Pos[2]]); + //recursively sample the second half of the curve + var param2 = this._checkIntersection(Vector.create([C0___,C1__,C2_,controlPts[3]]), midParam, endParam, point, radius); + if (param2!==null){ + return param2; + } + //no intersection, so return null + return null; +} - if (seg0Start.length===0 || seg0End.length ===0 || seg1Start.length===0 ||seg1End.length ===0){ - alert("empty offset point"); +//whether the point lies within the bbox given by the four control points +GLSubpath.prototype._isWithinBoundingBox = function(point, ctrlPts, radius) { + var bboxMin = Vector.create([Infinity, Infinity, Infinity]); + var bboxMax = Vector.create([-Infinity,-Infinity,-Infinity]); + for (var i=0;i bboxMax[d]){ + bboxMax[d] = ctrlPts[i][d]; } - - } //for (var i = 0; i < numSamples; i++) { - - - - return retOffSamples; + } } - - // _triangulateOffset - // generate triangles from offset points and add them to the offsetTriangles array - this._triangulateOffset = function (offsetPoints, offsetTriangles) { - if (offsetPoints.length === 0) - return; - - // triangulate using the fan method (trivial) for every consecutive pair of offset points - for (var i = 1; i < offsetPoints.length; i++) { - var tri1 = new SubpathOffsetTriangle(offsetPoints[i - 1].CurveMapPos, offsetPoints[i - 1].Pos, offsetPoints[i].CurveMapPos); - var tri2 = new SubpathOffsetTriangle(offsetPoints[i].CurveMapPos, offsetPoints[i - 1].Pos, offsetPoints[i].Pos); - offsetTriangles.push(tri1); - offsetTriangles.push(tri2); + //check whether the bbox of the control points contains the point within the specified radius + for (var d=0;d<3;d++){ + if (point[d] < (bboxMin[d]-radius)){ + return false; + } + if (point[d] > (bboxMax[d]+radius)){ + return false; } } + return true; +} - // _addOffsetSamples - // Adds samples to the offset path in places it is not sampled densely enough - //TODO: as an optimization, don't add any samples at a concave corners, since those extra points will be removed anyway - this._addOffsetSamples = function (offsetPoints) { - if (offsetPoints.length === 0) - return; - - var retOffsetPoints = []; - retOffsetPoints.push(offsetPoints[0]); - //compute angle between consecutive vectors from curve to offset - for (var i = 1; i < offsetPoints.length; i++) { - var prev = offsetPoints[i - 1]; - var curr = offsetPoints[i]; - var vec1 = VecUtils.vecSubtract(3, prev.Pos, prev.CurveMapPos); - var vec2 = VecUtils.vecSubtract(3, curr.Pos, curr.CurveMapPos); - var width = VecUtils.vecMag(3, vec1); - //NOTE: the following angle computation works only in 2D - var angle = Math.atan2(vec2[1], vec2[0]) - Math.atan2(vec1[1], vec1[0]); - //if (angle < 0) angle = angle + Math.PI + Math.PI; - angle = angle * 180 / Math.PI; - if (angle > 180) { - angle = angle - 360; - } - if (angle < -180) { - angle = 360 + angle; - } - var numNewSamples = Math.floor(Math.abs(angle) / this._MAX_OFFSET_ANGLE); - if (numNewSamples > 0) { - numNewSamples -= 1; //decrementing gives the correct number of new samples - } - if (numNewSamples > 10) { - numNewSamples = 10; //limit the number of inserted offset samples to 10 - } - - //build the rotation matrix - var rotAngle = angle / (numNewSamples + 1) * Math.PI / 180; //angle to rotate (in radians) - var rotMat = Matrix.RotationZ(rotAngle); - - //build the vector to be transformed - var rotVec = VecUtils.vecNormalize(3, vec1, 1); - - for (var s = 0; s < numNewSamples; s++) { - //build curve position as a linear combination of prev. and curr - var weight = (s + 1) / (numNewSamples + 1); - var scaledPos1 = Vector.create([prev.CurveMapPos[0], prev.CurveMapPos[1], prev.CurveMapPos[2]]); - VecUtils.vecScale(3, scaledPos1, 1 - weight); - var scaledPos2 = Vector.create([curr.CurveMapPos[0], curr.CurveMapPos[1], curr.CurveMapPos[2]]); - VecUtils.vecScale(3, scaledPos2, weight); - var curvePos = VecUtils.vecAdd(3, scaledPos1, scaledPos2); - - rotVec = MathUtils.transformVector(rotVec, rotMat); - var offsetPos = VecUtils.vecAdd(3, curvePos, VecUtils.vecNormalize(3, rotVec, width)); - var newOffsetPoint = new SubpathOffsetPoint(offsetPos, curvePos); - retOffsetPoints.push(newOffsetPoint); +GLSubpath.prototype.pickAnchor = function (pickX, pickY, pickZ, radius) { + var numAnchors = this._Anchors.length; + var selAnchorIndex = -1; + var retCode = this.SEL_NONE; + var radSq = radius * radius; + var minDistance = Infinity; + //check if the clicked location is close to the currently selected anchor position + if (this._selectedAnchorIndex>=0 && this._selectedAnchorIndex=0 && this._selectedAnchorIndexstartIndex+1 intersects the segment from endIndex-1->endIndex - var seg0Start = Vector.create([offSamples[i].Pos[0],offSamples[i].Pos[1],offSamples[i].Pos[2]]); - var nextI = (i+1)%numOrigPoints; - var seg0End = Vector.create([offSamples[nextI].Pos[0],offSamples[nextI].Pos[1],offSamples[nextI].Pos[2]]); - - for (var j=0;j 0.01 && Math.abs(param) > 0.01) //if the intersection is not at the endpoint - segmentIntersectionArray[i].paramArray.push(param); - } - }//for every point j - }//for every point i - - var retOffSamples = [] ;//what is built and returned - //sort all the intersection points based on the parameter value - //AND add the intersection points to the new offset samples - - for (var i=0;i=0 && selAnchorIndex === -1) { + var distSq = this._Anchors[this._selectedAnchorIndex].getPrevDistanceSq(pickX, pickY, pickZ); + if (distSq < minDistance && distSq < radSq){ + selAnchorIndex = this._selectedAnchorIndex; + minDistance = distSq; + retCode = retCode | this.SEL_PREV; + } else { + //check the next for this anchor point + distSq = this._Anchors[this._selectedAnchorIndex].getNextDistanceSq(pickX, pickY, pickZ); + if (distSq 0) { - P = Vector.create([this._samples[3 * (i - 1)], this._samples[3 * (i - 1) + 1], this._samples[3 * (i - 1) + 2]]); - } +GLSubpath.prototype.getBBoxMin = function () { return this._BBoxMin; } +GLSubpath.prototype.getBBoxMax = function () { return this._BBoxMax; } - //compute the direction at C (based on averaging across prev. and next if available) - var D = null; - if (N) { - D = VecUtils.vecSubtract(3, N, C); - if (P) { - var Dprev = VecUtils.vecSubtract(3, C, P); - D = VecUtils.vecAdd(3, D, Dprev); - D = VecUtils.vecScale(3, D, 0.5); - } - } - else { - D = VecUtils.vecSubtract(3, C, P); - } +GLSubpath.prototype.getStrokeWidth = function () { return this._strokeWidth; } +GLSubpath.prototype.setStrokeWidth = function (w) { this._strokeWidth = w; } +GLSubpath.prototype.getStrokeMaterial = function () { return this._strokeMaterial; } +GLSubpath.prototype.setStrokeMaterial = function (m) { this._strokeMaterial = m; } +GLSubpath.prototype.getStrokeColor = function () { return this._strokeColor; } +GLSubpath.prototype.setStrokeColor = function (c) { this._strokeColor = c; } +GLSubpath.prototype.getStrokeStyle = function () { return this._strokeStyle; } +GLSubpath.prototype.setStrokeStyle = function (s) { this._strokeStyle = s; } +GLSubpath.prototype.getFillMaterial = function() {return this._fillMaterial;} +GLSubpath.prototype.setFillMaterial = function(m){ this._fillMaterial = m;} +GLSubpath.prototype.getFillColor = function() {return this._fillColor;} +GLSubpath.prototype.setFillColor = function(c){this._fillColor = c;} - if (!D) { - throw ("null direction in _offsetFromSamples"); - return; - } - //ignore this point if the D is not significant - var dirLen = VecUtils.vecMag(3, D); - if (dirLen < this._SAMPLING_EPSILON) { - continue; - } - - D = VecUtils.vecNormalize(3, D, 1.0); - - //compute the perpendicular to D to the left - //TODO: for now assume we're only considering D in XY plane - var OL = Vector.create([D[1], -1*D[0], D[2]]); - OL = VecUtils.vecNormalize(3, OL, width); - var OR = Vector.create([-1 * OL[0], -1 * OL[1], -1 * OL[2]]); - OR = VecUtils.vecNormalize(3, OR, width); - var leftC = VecUtils.vecAdd(3, C, OL); - var rightC = VecUtils.vecAdd(3, C, OR); - - var sopl = new SubpathOffsetPoint(leftC, C); - this._offsetPointsLeft.push(sopl); - if (sopl.Pos.length===0 || sopl.CurveMapPos.length ===0){ - alert("empty offset point"); - } - var sopr = new SubpathOffsetPoint(rightC, C); - this._offsetPointsRight.push(sopr); - } //for (var i = 0; i < numPoints; i++) { +GLSubpath.prototype.setWidth = function () {//NO-OP for now +} +GLSubpath.prototype.setHeight = function () {//NO-OP for now +} +GLSubpath.prototype.copyFromSubpath = function (subpath) { + this.clearAllAnchors(); + for (var i = 0; i < subpath.getNumAnchors(); i++) { + var oldAnchor = subpath.getAnchor(i); + var newAnchor = new GLAnchorPoint(); + newAnchor.setPos(oldAnchor.getPosX(), oldAnchor.getPosY(), oldAnchor.getPosZ()); + newAnchor.setPrevPos(oldAnchor.getPrevX(), oldAnchor.getPrevY(), oldAnchor.getPrevZ()); + newAnchor.setNextPos(oldAnchor.getNextX(), oldAnchor.getNextY(), oldAnchor.getNextZ()); + this.addAnchor(newAnchor); + } + this.setIsClosed(subpath.getIsClosed()); + this.setStrokeWidth(subpath.getStrokeWidth()); +} - //add offset samples near cusps or corners - this._offsetPointsLeft = this._addOffsetSamples(this._offsetPointsLeft); - this._offsetPointsRight = this._addOffsetSamples(this._offsetPointsRight); +GLSubpath.prototype.translateAnchors = function (tx, ty, tz) { + for (var i=0;i