diff options
author | Pushkar Joshi | 2012-04-10 14:18:02 -0700 |
---|---|---|
committer | Pushkar Joshi | 2012-04-10 14:18:02 -0700 |
commit | 7fed1940bb4f3a333cef92fd51787a29e6dd787b (patch) | |
tree | b7d5298604e8e1d323c51b2729f1f98daea31e6a | |
parent | dae3041e6b8269da3d593a44c09e2288bb434a02 (diff) | |
download | ninja-7fed1940bb4f3a333cef92fd51787a29e6dd787b.tar.gz |
compute and store local coordinates for all anchor points and their control handles separately, AND more hit testing with local coordinates
-rwxr-xr-x | js/lib/geom/sub-path.js | 165 | ||||
-rwxr-xr-x | js/tools/PenTool.js | 76 |
2 files changed, 175 insertions, 66 deletions
diff --git a/js/lib/geom/sub-path.js b/js/lib/geom/sub-path.js index 33bcfc9a..64f27bd3 100755 --- a/js/lib/geom/sub-path.js +++ b/js/lib/geom/sub-path.js | |||
@@ -57,7 +57,7 @@ var GLSubpath = function GLSubpath() { | |||
57 | this._LocalPoints = []; //polyline representation of this curve in canvas space | 57 | this._LocalPoints = []; //polyline representation of this curve in canvas space |
58 | this._LocalBBoxMin = [0,0,0]; //bbox min point of _LocalPoints | 58 | this._LocalBBoxMin = [0,0,0]; //bbox min point of _LocalPoints |
59 | this._LocalBBoxMax = [0,0,0]; //bbox max point of _LocalPoints | 59 | this._LocalBBoxMax = [0,0,0]; //bbox max point of _LocalPoints |
60 | 60 | this._AnchorLocalCoords = []; //local coords for all the anchor points , stored as a triplet of 3D vectors (prev, curr, next, in order) | |
61 | this._UnprojectedAnchors = []; | 61 | this._UnprojectedAnchors = []; |
62 | 62 | ||
63 | //initially set the _dirty bit so we will construct samples | 63 | //initially set the _dirty bit so we will construct samples |
@@ -66,6 +66,9 @@ var GLSubpath = function GLSubpath() { | |||
66 | //initially set the local dirty bit so we will construct local coordinates | 66 | //initially set the local dirty bit so we will construct local coordinates |
67 | this._isLocalDirty = true; | 67 | this._isLocalDirty = true; |
68 | 68 | ||
69 | //initially set the local dirty bit for the anchors so we will construct local coordinates | ||
70 | this._isAnchorLocalDirty = true; | ||
71 | |||
69 | //whether or not to use the canvas drawing to stroke/fill | 72 | //whether or not to use the canvas drawing to stroke/fill |
70 | this._useCanvasDrawing = true; | 73 | this._useCanvasDrawing = true; |
71 | 74 | ||
@@ -92,8 +95,8 @@ var GLSubpath = function GLSubpath() { | |||
92 | 95 | ||
93 | //tool that owns this subpath | 96 | //tool that owns this subpath |
94 | this._drawingTool = null; | 97 | this._drawingTool = null; |
95 | this._planeMat = null; | 98 | this._planeMat = Matrix.I(4); |
96 | this._planeMatInv = null; | 99 | this._planeMatInv = Matrix.I(4); |
97 | this._planeCenter = null; | 100 | this._planeCenter = null; |
98 | this._dragPlane = null; | 101 | this._dragPlane = null; |
99 | 102 | ||
@@ -498,6 +501,27 @@ GLSubpath.prototype.insertAnchorAtParameter = function(index, param) { | |||
498 | this._dirty = true; | 501 | this._dirty = true; |
499 | }; | 502 | }; |
500 | 503 | ||
504 | GLSubpath.prototype._checkIntersectionWithSampleLocalCoord = function(startIndex, endIndex, point, radius){ | ||
505 | //check whether the point is within the radius distance from the curve represented as a polyline in _samples | ||
506 | //return the parametric distance along the curve if there is an intersection, else return null | ||
507 | //will assume that the BBox test is performed outside this function | ||
508 | if (endIndex<startIndex){ | ||
509 | //go from startIndex to the end of the samples | ||
510 | endIndex = this._LocalPoints.length; | ||
511 | } | ||
512 | for (var i=startIndex; i<endIndex-1; i++){ | ||
513 | var seg0 = this._LocalPoints[i].slice(0); | ||
514 | var j=i+1; | ||
515 | var seg1 = this._LocalPoints[j].slice(0); | ||
516 | var distToSegment = MathUtils.distPointToSegment(point, seg0, seg1); | ||
517 | if (distToSegment<=radius){ | ||
518 | var paramDistance = MathUtils.paramPointProjectionOnSegment(point, seg0, seg1); //TODO Optimize! this function was called in distPointToSegment above | ||
519 | return this._sampleParam[i] + (this._sampleParam[j] - this._sampleParam[i])*paramDistance; | ||
520 | } | ||
521 | } | ||
522 | return null; | ||
523 | }; | ||
524 | |||
501 | GLSubpath.prototype._checkIntersectionWithSamples = function(startIndex, endIndex, point, radius){ | 525 | GLSubpath.prototype._checkIntersectionWithSamples = function(startIndex, endIndex, point, radius){ |
502 | //check whether the point is within the radius distance from the curve represented as a polyline in _samples | 526 | //check whether the point is within the radius distance from the curve represented as a polyline in _samples |
503 | //return the parametric distance along the curve if there is an intersection, else return null | 527 | //return the parametric distance along the curve if there is an intersection, else return null |
@@ -622,23 +646,24 @@ GLSubpath.prototype._isWithinBoundingBox = function(point, ctrlPts, radius) { | |||
622 | 646 | ||
623 | GLSubpath.prototype._checkAnchorIntersection = function(pickX, pickY, pickZ, radSq, anchorIndex, minDistance, useLocal) { | 647 | GLSubpath.prototype._checkAnchorIntersection = function(pickX, pickY, pickZ, radSq, anchorIndex, minDistance, useLocal) { |
624 | //if we are asked to use the local coordinate and the local coordinate for this anchor exists | 648 | //if we are asked to use the local coordinate and the local coordinate for this anchor exists |
625 | if (useLocal && this._anchorSampleIndex.length>anchorIndex && this._LocalPoints.length > this._anchorSampleIndex[anchorIndex]) { | 649 | if (useLocal && this._AnchorLocalCoords.length > anchorIndex) {//this._anchorSampleIndex.length>anchorIndex && this._LocalPoints.length > this._anchorSampleIndex[anchorIndex]) { |
626 | var localCoord = this._LocalPoints[this._anchorSampleIndex[anchorIndex]] | 650 | //var localCoord = this._LocalPoints[this._anchorSampleIndex[anchorIndex]]; |
627 | var distSq = VecUtils.vecDistSq(3, [pickX, pickY, pickZ], localCoord); | 651 | var anchorLocalCoord = this._AnchorLocalCoords[anchorIndex]; |
652 | var distSq = VecUtils.vecDistSq(3, [pickX, pickY, pickZ], anchorLocalCoord[1]); | ||
628 | //check the anchor point | 653 | //check the anchor point |
629 | if (distSq < radSq && distSq<minDistance) { | 654 | if (distSq < radSq && distSq<minDistance) { |
630 | return this.SEL_ANCHOR; | 655 | return this.SEL_ANCHOR; |
631 | } | 656 | } |
632 | /* | 657 | |
633 | //check the prev. and next of the selected anchor point | 658 | //check the prev. and next of the selected anchor point |
634 | distSq = this._Anchors[anchorIndex].getPrevDistanceSq(pickX, pickY, pickZ); | 659 | distSq = VecUtils.vecDistSq(3, [pickX, pickY, pickZ], anchorLocalCoord[0]); |
635 | if (distSq<radSq && distSq<minDistance){ | 660 | if (distSq<radSq && distSq<minDistance){ |
636 | return this.SEL_PREV; | 661 | return this.SEL_PREV; |
637 | } | 662 | } |
638 | distSq = this._Anchors[anchorIndex].getNextDistanceSq(pickX, pickY, pickZ); | 663 | distSq = VecUtils.vecDistSq(3, [pickX, pickY, pickZ], anchorLocalCoord[2]); |
639 | if (distSq<radSq && distSq<minDistance){ | 664 | if (distSq<radSq && distSq<minDistance){ |
640 | return this.SEL_NEXT; | 665 | return this.SEL_NEXT; |
641 | }*/ | 666 | } |
642 | return this.SEL_NONE; | 667 | return this.SEL_NONE; |
643 | } | 668 | } |
644 | 669 | ||
@@ -694,6 +719,42 @@ GLSubpath.prototype.isWithinBBox = function(x,y,z) { | |||
694 | return true; | 719 | return true; |
695 | } | 720 | } |
696 | 721 | ||
722 | //pick the path location closest to the input pick point (in local coord) | ||
723 | GLSubpath.prototype.pathSamplesLocalHitTest = function(pickX, pickY, pickZ, radius){ | ||
724 | var numAnchors = this._AnchorLocalCoords.length; | ||
725 | var selAnchorIndex = -1; | ||
726 | var retParam = null; | ||
727 | var radSq = radius * radius; | ||
728 | var minDistance = Infinity; | ||
729 | |||
730 | //first check if the input location is within the bounding box | ||
731 | var numSegments = this._isClosed ? numAnchors : numAnchors-1; | ||
732 | var currAnchor = this._AnchorLocalCoords[0]; | ||
733 | var nextAnchor=null; | ||
734 | for (var i = 0; i < numSegments; i++) { | ||
735 | var nextIndex = (i+1)%numAnchors; | ||
736 | nextAnchor = this._AnchorLocalCoords[nextIndex]; | ||
737 | //check if the point is close to the bezier segment between anchor i and anchor nextIndex | ||
738 | var controlPoints = [currAnchor[1].slice(0), | ||
739 | currAnchor[2].slice(0), | ||
740 | nextAnchor[0].slice(0), | ||
741 | nextAnchor[1].slice(0)]; | ||
742 | var point = [pickX, pickY, pickZ]; | ||
743 | if (this._isWithinBoundingBox(point, controlPoints, radius)) { | ||
744 | //var intersectParam = this._checkIntersection(controlPoints, 0.0, 1.0, point, radius); | ||
745 | var intersectParam = this._checkIntersectionWithSampleLocalCoord(this._anchorSampleIndex[i], this._anchorSampleIndex[nextIndex], point, radius); | ||
746 | if (intersectParam){ | ||
747 | selAnchorIndex=i; | ||
748 | retParam = intersectParam-i; //make the retParam go from 0 to 1 | ||
749 | break; | ||
750 | } | ||
751 | } | ||
752 | currAnchor = nextAnchor; | ||
753 | }//for every anchor i | ||
754 | return [selAnchorIndex,retParam]; | ||
755 | |||
756 | }; | ||
757 | |||
697 | //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 | 758 | //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 |
698 | GLSubpath.prototype.pathHitTest = function (pickX, pickY, pickZ, radius) { | 759 | GLSubpath.prototype.pathHitTest = function (pickX, pickY, pickZ, radius) { |
699 | var numAnchors = this._Anchors.length; | 760 | var numAnchors = this._Anchors.length; |
@@ -755,7 +816,6 @@ GLSubpath.prototype.pathHitTest = function (pickX, pickY, pickZ, radius) { | |||
755 | if (this._isWithinBoundingBox(point, controlPoints, radius)) { | 816 | if (this._isWithinBoundingBox(point, controlPoints, radius)) { |
756 | //var intersectParam = this._checkIntersection(controlPoints, 0.0, 1.0, point, radius); | 817 | //var intersectParam = this._checkIntersection(controlPoints, 0.0, 1.0, point, radius); |
757 | var intersectParam = this._checkIntersectionWithSamples(this._anchorSampleIndex[i], this._anchorSampleIndex[nextIndex], point, radius); | 818 | var intersectParam = this._checkIntersectionWithSamples(this._anchorSampleIndex[i], this._anchorSampleIndex[nextIndex], point, radius); |
758 | console.log("intersectParam:"+intersectParam); | ||
759 | if (intersectParam){ | 819 | if (intersectParam){ |
760 | selAnchorIndex=i; | 820 | selAnchorIndex=i; |
761 | retParam = intersectParam-i; //make the retParam go from 0 to 1 | 821 | retParam = intersectParam-i; //make the retParam go from 0 to 1 |
@@ -1145,13 +1205,24 @@ GLSubpath.prototype._unprojectPt = function(pt, pespectiveDist) { | |||
1145 | 1205 | ||
1146 | //return the local coord. of the anchor at the specified index, null if the anchor does not have a local coord yet | 1206 | //return the local coord. of the anchor at the specified index, null if the anchor does not have a local coord yet |
1147 | GLSubpath.prototype.getAnchorLocalCoord = function(index){ | 1207 | GLSubpath.prototype.getAnchorLocalCoord = function(index){ |
1148 | if (this._isDirty){ | 1208 | if (this._dirty){ |
1149 | this.createSamples(); | 1209 | this.createSamples(); |
1150 | } | 1210 | } |
1151 | if (this._isLocalDirty){ | 1211 | if (this._isLocalDirty){ |
1152 | this.buildLocalCoord(); | 1212 | this.buildLocalCoord(); |
1153 | } | 1213 | } |
1154 | if (index<0 || index>= this._Anchors.length || index>=this._anchorSampleIndex.length){ | 1214 | if (index<0 || index>= this._Anchors.length){ |
1215 | return null; | ||
1216 | } | ||
1217 | |||
1218 | if (index>= this._AnchorLocalCoords.length || this._isAnchorLocalDirty){ | ||
1219 | this._isAnchorLocalDirty = true; | ||
1220 | this.buildAnchorLocalCoord(); | ||
1221 | } | ||
1222 | |||
1223 | return this._AnchorLocalCoords[index]; | ||
1224 | /* | ||
1225 | if (index>=this._anchorSampleIndex.length){ | ||
1155 | return null; | 1226 | return null; |
1156 | } | 1227 | } |
1157 | var anchorSampleIndex = this._anchorSampleIndex[index]; | 1228 | var anchorSampleIndex = this._anchorSampleIndex[index]; |
@@ -1160,6 +1231,55 @@ GLSubpath.prototype.getAnchorLocalCoord = function(index){ | |||
1160 | } | 1231 | } |
1161 | var localCoord = this._LocalPoints[anchorSampleIndex].slice(0); | 1232 | var localCoord = this._LocalPoints[anchorSampleIndex].slice(0); |
1162 | return localCoord; | 1233 | return localCoord; |
1234 | */ | ||
1235 | }; | ||
1236 | |||
1237 | GLSubpath.prototype.buildAnchorLocalCoord = function() | ||
1238 | { | ||
1239 | if (!this._isAnchorLocalDirty){ | ||
1240 | return; //nothing to do | ||
1241 | } | ||
1242 | var ViewUtils = require("js/helper-classes/3D/view-utils").ViewUtils; | ||
1243 | var SnapManager = require("js/helper-classes/3D/snap-manager").SnapManager; | ||
1244 | |||
1245 | var widthAdjustment = -SnapManager.getStageWidth()*0.5; | ||
1246 | var heightAdjustment = -SnapManager.getStageHeight()*0.5; | ||
1247 | |||
1248 | var drawingCanvas = this.getCanvas(); | ||
1249 | if (!drawingCanvas){ | ||
1250 | drawingCanvas = ViewUtils.getStageElement(); | ||
1251 | } | ||
1252 | var localToStageWorldMat = ViewUtils.getLocalToStageWorldMatrix(drawingCanvas, true, false); | ||
1253 | var stageWorldToLocalMat = glmat4.inverse(localToStageWorldMat, []); | ||
1254 | var numAnchors = this._Anchors.length; | ||
1255 | this._AnchorLocalCoords = []; | ||
1256 | var i=0, currAnchor, prevLocalCoord, currLocalCoord, nextLocalCoord; | ||
1257 | for (i=0;i<numAnchors;i++){ | ||
1258 | //get the local coordinate of this anchor point | ||
1259 | currAnchor = this._Anchors[i]; | ||
1260 | //convert all three positions assoc. with this anchor from stage world to local coord | ||
1261 | prevLocalCoord = MathUtils.transformAndDivideHomogeneousPoint([ | ||
1262 | currAnchor.getPrevX()+widthAdjustment, | ||
1263 |