From d7d78d4a4e8cf82c56379d25efbe679b3b823abc Mon Sep 17 00:00:00 2001
From: Pushkar Joshi
Date: Thu, 1 Mar 2012 11:22:46 -0800
Subject: show feedback on mouseover on a part of the path where a new anchor
 will be added if clicked

---
 images/cursors/penAdd.png           | Bin 0 -> 3043 bytes
 js/helper-classes/RDGE/GLSubpath.js | 147 ++++++++++++++++++++++++++++++------
 js/tools/PenTool.js                 |  12 ++-
 3 files changed, 135 insertions(+), 24 deletions(-)
 create mode 100644 images/cursors/penAdd.png

diff --git a/images/cursors/penAdd.png b/images/cursors/penAdd.png
new file mode 100644
index 00000000..c306cc85
Binary files /dev/null and b/images/cursors/penAdd.png differ
diff --git a/js/helper-classes/RDGE/GLSubpath.js b/js/helper-classes/RDGE/GLSubpath.js
index f3d8ad36..2fb91d33 100755
--- a/js/helper-classes/RDGE/GLSubpath.js
+++ b/js/helper-classes/RDGE/GLSubpath.js
@@ -505,7 +505,6 @@ GLSubpath.prototype._isWithinBoundingBox = function(point, ctrlPts, radius) {
 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
@@ -515,9 +514,23 @@ GLSubpath.prototype.pickAnchor = function (pickX, pickY, pickZ, radius) {
         if (distSq < minDistance && distSq < radSq) {
             selAnchorIndex = this._selectedAnchorIndex;
             minDistance = distSq;
-            retCode = retCode | this.SEL_ANCHOR;
+        } else {
+            //check the prev and next of the selected anchor if the above did not register a hit
+            distSq = this._Anchors[this._selectedAnchorIndex].getPrevDistanceSq(pickX, pickY, pickZ);
+            if (distSq < minDistance && distSq < radSq){
+                selAnchorIndex = this._selectedAnchorIndex;
+                minDistance = distSq;
+            } else {
+                //check the next for this anchor point
+                distSq = this._Anchors[this._selectedAnchorIndex].getNextDistanceSq(pickX, pickY, pickZ);
+                if (distSq<minDistance && distSq<radSq){
+                    selAnchorIndex = this._selectedAnchorIndex;
+                    minDistance = distSq;
+                }
+            }
         }
     }
+    
     //now check if the click location is close to any anchor position
     if (selAnchorIndex===-1) {
         for (var i = 0; i < numAnchors; i++) {
@@ -532,12 +545,97 @@ GLSubpath.prototype.pickAnchor = function (pickX, pickY, pickZ, radius) {
     return selAnchorIndex;
 }
 
+GLSubpath.prototype.isWithinBBox =function(x,y,z){
+    if (this._BBoxMin[0]>x || this._BBoxMin[1]>y || this._BBoxMin[2]>z){
+        return false;
+    }
+    if (this._BBoxMax[0]<x || this._BBoxMax[1]<y || this._BBoxMax[2]<z){
+        return false;
+    }
+    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
+GLSubpath.prototype.pathHitTest = function (pickX, pickY, pickZ, radius) {
+    var numAnchors = this._Anchors.length;
+    var selAnchorIndex = -1;
+    var retParam = null;
+    var radSq = radius * radius;
+    var minDistance = Infinity;
+    
+    //check if the location is close to the currently selected anchor position
+    if (this._selectedAnchorIndex>=0 && this._selectedAnchorIndex<this._Anchors.length){
+        var distSq = this._Anchors[this._selectedAnchorIndex].getDistanceSq(pickX, pickY, pickZ);
+        //check the anchor point
+        if (distSq < minDistance && distSq < radSq) {
+            selAnchorIndex = this._selectedAnchorIndex;
+            minDistance = distSq;
+        }
+    }
+    //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;
+        } else {
+            //check the next for this anchor point
+            distSq = this._Anchors[this._selectedAnchorIndex].getNextDistanceSq(pickX, pickY, pickZ);
+            if (distSq<minDistance && distSq<radSq){
+                selAnchorIndex = this._selectedAnchorIndex;
+                minDistance = distSq;
+            }
+        }
+    }
+
+    //now check if the location is close to any anchor position
+    if (selAnchorIndex===-1) {
+        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;
+            }
+        }//for every anchor i
+    }
+
+    //finally check if the location is close to the curve itself
+    if (selAnchorIndex===-1) {
+        //first check if the input location is within the bounding box
+        if (this.isWithinBBox(pickX,pickY,pickZ)){
+            var numSegments = this._isClosed ? numAnchors : numAnchors-1;
+            for (var i = 0; i < numSegments; i++) {
+                var nextIndex = (i+1)%numAnchors;
+                //check if the point is close to the bezier segment between anchor i and anchor nextIndex
+                var controlPoints = Vector.create([Vector.create([this._Anchors[i].getPosX(),this._Anchors[i].getPosY(),this._Anchors[i].getPosZ()]),
+                    Vector.create([this._Anchors[i].getNextX(),this._Anchors[i].getNextY(),this._Anchors[i].getNextZ()]),
+                    Vector.create([this._Anchors[nextIndex].getPrevX(),this._Anchors[nextIndex].getPrevY(),this._Anchors[nextIndex].getPrevZ()]),
+                    Vector.create([this._Anchors[nextIndex].getPosX(),this._Anchors[nextIndex].getPosY(),this._Anchors[nextIndex].getPosZ()])]);
+                var point = Vector.create([pickX, pickY, pickZ]);
+                if (this._isWithinBoundingBox(point, controlPoints, radius)) {
+                    //var intersectParam = this._checkIntersection(controlPoints, 0.0, 1.0, point, radius);
+                    var intersectParam = this._checkIntersectionWithSamples(this._anchorSampleIndex[i], this._anchorSampleIndex[nextIndex], point, radius);
+                    console.log("intersectParam:"+intersectParam);
+                    if (intersectParam){
+                        selAnchorIndex=i;
+                        retParam = intersectParam-i; //make the retParam go from 0 to 1
+                        break;
+                    }
+                }
+            }//for every anchor i
+        }//if is within bbox
+    }
+    return [selAnchorIndex,retParam];
+}     //GLSubpath.pathHitTest function
+
 
 //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
 GLSubpath.prototype.pickPath = function (pickX, pickY, pickZ, radius) {
     var numAnchors = this._Anchors.length;
     var selAnchorIndex = -1;
     var retCode = this.SEL_NONE;
+    var retParam = null;
     var radSq = radius * radius;
     var minDistance = Infinity;
     //check if the clicked location is close to the currently selected anchor position
@@ -580,33 +678,36 @@ GLSubpath.prototype.pickPath = function (pickX, pickY, pickZ, radius) {
             }
         }
     }
-    var retParam = null;
+
     if (retCode !== this.SEL_NONE) {
         retCode = retCode | this.SEL_PATH; //ensure that path is also selected if anything else is selected
         this._selectedAnchorIndex = selAnchorIndex;
     } else {
         this._selectedAnchorIndex = -1;
-        var numSegments = this._isClosed ? numAnchors : numAnchors-1;
-        for (var i = 0; i < numSegments; i++) {
-            var nextIndex = (i+1)%numAnchors;
-            //check if the point is close to the bezier segment between anchor i and anchor nextIndex
-            var controlPoints = Vector.create([Vector.create([this._Anchors[i].getPosX(),this._Anchors[i].getPosY(),this._Anchors[i].getPosZ()]),
-                Vector.create([this._Anchors[i].getNextX(),this._Anchors[i].getNextY(),this._Anchors[i].getNextZ()]),
-                Vector.create([this._Anchors[nextIndex].getPrevX(),this._Anchors[nextIndex].getPrevY(),this._Anchors[nextIndex].getPrevZ()]),
-                Vector.create([this._Anchors[nextIndex].getPosX(),this._Anchors[nextIndex].getPosY(),this._Anchors[nextIndex].getPosZ()])]);
-            var point = Vector.create([pickX, pickY, pickZ]);
-            if (this._isWithinBoundingBox(point, controlPoints, radius)) {
-                //var intersectParam = this._checkIntersection(controlPoints, 0.0, 1.0, point, radius);
-                var intersectParam = this._checkIntersectionWithSamples(this._anchorSampleIndex[i], this._anchorSampleIndex[nextIndex], point, radius);
-                console.log("intersectParam:"+intersectParam);
-                if (intersectParam){
-                    retCode = retCode | this.SEL_PATH;
-                    retParam = intersectParam-i; //make the retParam go from 0 to 1
-                    this._selectedAnchorIndex = i;
-                    break;
+        //first check if the input location is within the bounding box
+        if (this.isWithinBBox(pickX,pickY,pickZ)){
+            var numSegments = this._isClosed ? numAnchors : numAnchors-1;
+            for (var i = 0; i < numSegments; i++) {
+                var nextIndex = (i+1)%numAnchors;
+                //check if the point is close to the bezier segment between anchor i and anchor nextIndex
+                var controlPoints = Vector.create([Vector.create([this._Anchors[i].getPosX(),this._Anchors[i].getPosY(),this._Anchors[i].getPosZ()]),
+                    Vector.create([this._Anchors[i].getNextX(),this._Anchors[i].getNextY(),this._Anchors[i].getNextZ()]),
+                    Vector.create([this._Anchors[nextIndex].getPrevX(),this._Anchors[nextIndex].getPrevY(),this._Anchors[nextIndex].getPrevZ()]),
+                    Vector.create([this._Anchors[nextIndex].getPosX(),this._Anchors[nextIndex].getPosY(),this._Anchors[nextIndex].getPosZ()])]);
+                var point = Vector.create([pickX, pickY, pickZ]);
+                if (this._isWithinBoundingBox(point, controlPoints, radius)) {
+                    //var intersectParam = this._checkIntersection(controlPoints, 0.0, 1.0, point, radius);
+                    var intersectParam = this._checkIntersectionWithSamples(this._anchorSampleIndex[i], this._anchorSampleIndex[nextIndex], point, radius);
+                    console.log("intersectParam:"+intersectParam);
+                    if (intersectParam){
+                        retCode = retCode | this.SEL_PATH;
+                        retParam = intersectParam-i; //make the retParam go from 0 to 1
+                        this._selectedAnchorIndex = i;
+                        break;
+                    }
                 }
-            }
-        }//for every anchor i
+            }//for every anchor i
+        }//is within bounding box
     }
     this._selectMode = retCode;
     return retParam;
diff --git a/js/tools/PenTool.js b/js/tools/PenTool.js
index ddc8bc04..02367328 100755
--- a/js/tools/PenTool.js
+++ b/js/tools/PenTool.js
@@ -275,6 +275,9 @@ exports.PenTool = Montage.create(ShapeTool, {
             this.application.ninja.stage.clearDrawingCanvas();
             this._hoveredAnchorIndex = -1;
 
+            //set the cursor to be the default cursor
+            this.application.ninja.stage.drawingCanvas.style.cursor = "auto";
+
             if (this._isDrawing) {
                 var point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas, new WebKitPoint(event.pageX, event.pageY));
                 //go through the drawing toolbase to get the position of the mouse 
@@ -345,6 +348,14 @@ exports.PenTool = Montage.create(ShapeTool, {
                     var selAnchor = this._selectedSubpath.pickAnchor(currMousePos[0], currMousePos[1], currMousePos[2], this._PICK_POINT_RADIUS);
                     if (selAnchor >=0) {
                         this._hoveredAnchorIndex = selAnchor;
+                    } else {
+                        //detect if the current mouse position will hit the path
+                        var pathHitTestData = this._selectedSubpath.pathHitTest(currMousePos[0], currMousePos[1], currMousePos[2], this._PICK_POINT_RADIUS);
+                        if (pathHitTestData[0]!==-1){
+                            //change the cursor
+                            var cursor = "url('images/cursors/penAdd.png') 10 10,default";
+                            this.application.ninja.stage.drawingCanvas.style.cursor = cursor;
+                        }
                     }
                 }
             } //else of if (this._isDrawing) {
@@ -353,7 +364,6 @@ exports.PenTool = Montage.create(ShapeTool, {
             if (this._selectedSubpath){
                 this.DrawSubpathAnchors(this._selectedSubpath);
             }
-
         }//value: function(event)
     },//HandleMouseMove
 
-- 
cgit v1.2.3


From 0b7f2f54738d2c1ea480b9bac7d3a750b1ef4df6 Mon Sep 17 00:00:00 2001
From: Pushkar Joshi
Date: Thu, 1 Mar 2012 15:29:17 -0800
Subject: first attempt at rendering the brush stroke as a collection of
 polylines (number of polylines equals the stroke width)

---
 js/helper-classes/RDGE/GLBrushStroke.js | 47 +++++++++++++++++++++++++++++++--
 js/tools/BrushTool.js                   |  4 +--
 2 files changed, 47 insertions(+), 4 deletions(-)

diff --git a/js/helper-classes/RDGE/GLBrushStroke.js b/js/helper-classes/RDGE/GLBrushStroke.js
index 5d773c2d..f5b0bce1 100755
--- a/js/helper-classes/RDGE/GLBrushStroke.js
+++ b/js/helper-classes/RDGE/GLBrushStroke.js
@@ -139,7 +139,8 @@ function GLBrushStroke() {
             var numPoints = this._Points.length;
 
             //**** add samples to the path if needed...linear interpolation for now
-            if (numPoints>1) {
+            //if (numPoints>1) {
+            if (0){
                 var threshold = this._WETNESS_FACTOR*this._strokeWidth;
                 var prevPt = this._Points[0];
                 var prevIndex = 0;
@@ -331,7 +332,7 @@ function GLBrushStroke() {
         }
         */
 
-
+        /*
         var R2 = this._strokeWidth;
         var R = R2*0.5;
         var hardness = 0; //for a pencil, this is always 1 //TODO get hardness parameter from user interface
@@ -359,6 +360,48 @@ function GLBrushStroke() {
             //ctx.globalCompositeOperation = 'source-in';
             //ctx.rect(x-R, y-R, R2, R2);
         }
+        */
+
+        //todo test how to render the path as a bunch of moveTo and lineTos (for calligraphic brush styles)
+        var numTraces = this._strokeWidth;
+        var halfNumTraces = numTraces/2;
+        var deltaDisplacement = [1,0];//[this._strokeWidth/numTraces, 0]; //a horizontal line brush
+        var startPos = [-this._strokeWidth/2,0];
+        for (var t=0;t<numTraces;t++){
+            var disp = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]];
+            //ctx.globalCompositeOperation = 'source-over';
+            var distFromMiddle = Math.abs(halfNumTraces-t);
+            var alphaVal = 1.0 - (distFromMiddle/halfNumTraces);
+            ctx.save();
+            ctx.lineWidth=this._strokeWidth/10;//4;
+            if (ctx.lineWidth<2)
+                ctx.lineWidth=2;
+            ctx.lineJoin="bevel";
+            ctx.lineCap="butt";
+            if (t<numTraces/2)
+                ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")";
+            else
+                ctx.strokeStyle="rgba("+255+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")";
+            ctx.translate(disp[0],disp[1]);
+            ctx.beginPath();
+            ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]);
+            for (var i=0;i<numPoints;i++){
+                ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]);
+            }
+            ctx.stroke();
+            ctx.restore();
+        }
+
+        /*
+        ctx.beginPath();
+        ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]);
+        for (var i=1;i<numPoints;i++){
+            ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]);
+        }
+        ctx.lineWidth=1.0;
+        ctx.strokeStyle = "black";
+        ctx.stroke();
+        */
 
         ctx.restore();
     } //render()
diff --git a/js/tools/BrushTool.js b/js/tools/BrushTool.js
index fec89eb2..dc9e022e 100644
--- a/js/tools/BrushTool.js
+++ b/js/tools/BrushTool.js
@@ -72,7 +72,7 @@ exports.BrushTool = Montage.create(ShapeTool, {
                     this._selectedBrushStroke.addPoint(currMousePos);
 
                     //TODO get these values from the options
-                    this._selectedBrushStroke.setStrokeWidth(20);
+                    this._selectedBrushStroke.setStrokeWidth(10);
                 }
                 NJevent("enableStageMove");//stageManagerModule.stageManager.enableMouseMove();
             } //value: function (event) {
@@ -112,7 +112,7 @@ exports.BrushTool = Montage.create(ShapeTool, {
 
                 if (this._isDrawing) {
                     var currMousePos = this._getUnsnappedPosition(event.pageX, event.pageY);
-                    if (this._selectedBrushStroke && this._selectedBrushStroke.getNumPoints()<100){
+                    if (this._selectedBrushStroke && this._selectedBrushStroke.getNumPoints()<1000){
                        this._selectedBrushStroke.addPoint(currMousePos);
                     }
                     this.ShowCurrentBrushStrokeOnStage();
-- 
cgit v1.2.3


From 1b68bb87c458877cb850a96d8a093d6064bc41dc Mon Sep 17 00:00:00 2001
From: Pushkar Joshi
Date: Fri, 2 Mar 2012 12:23:44 -0800
Subject: Catmull-Rom spline sampling for the brush stroke, and options for
 stroke size, stroke hardness and both stroke colors (left and right ---
 temporarily using the stroke and fill colors respectively)

---
 .../brush-properties.reel/brush-properties.html    | 35 ++++++++++-
 .../brush-properties.reel/brush-properties.js      | 17 ++---
 js/helper-classes/RDGE/GLBrushStroke.js            | 72 ++++++++++++++++++++--
 js/tools/BrushTool.js                              | 17 ++++-
 4 files changed, 120 insertions(+), 21 deletions(-)

diff --git a/js/components/tools-properties/brush-properties.reel/brush-properties.html b/js/components/tools-properties/brush-properties.reel/brush-properties.html
index 38f7f856..af07b3f2 100755
--- a/js/components/tools-properties/brush-properties.reel/brush-properties.html
+++ b/js/components/tools-properties/brush-properties.reel/brush-properties.html
@@ -11,12 +11,39 @@
 
         <script type="text/montage-serialization">
         {
+            "strokeSizeHT": {
+                "module": "js/components/hottextunit.reel",
+                "name": "HotTextUnit",
+                "properties": {
+                    "element": {"#": "strokeSize"},
+                    "minValue": 1,
+                    "maxValue": 100,
+                    "value": 1,
+                    "decimalPlace": 10,
+                    "acceptableUnits" : ["px", "pt"]
+                }
+            },
+
+            "strokeHardnessHT": {
+                "module": "js/components/hottextunit.reel",
+                "name": "HotTextUnit",
+                "properties": {
+                    "element": {"#": "strokeHardness"},
+                    "minValue": 0,
+                    "maxValue": 100,
+                    "value": 100,
+                    "decimalPlace": 10,
+                    "acceptableUnits" : ["px", "pt"]
+                }
+            },
 
             "owner": {
                 "module": "js/components/tools-properties/brush-properties.reel",
                 "name": "BrushProperties",
                 "properties": {
-                    "element": {"#": "brushProperties"}
+                    "element": {"#": "brushProperties"},
+                    "_strokeSize": {"@": "strokeSizeHT"},
+                    "_strokeHardness": {"@": "strokeHardnessHT"}
                 }
             }
     	}
@@ -26,6 +53,12 @@
 
     <body>
         <div id="brushProperties" class="subToolHolderPanel">
+            <div id="strokesContainer" class="leftLabel" style="margin-left:25px; padding-top: 3px;">
+                <label class="label"> Stroke:</label>
+                <div id="strokeSize" class="label"></div>
+                <label class="label"> Hardness:</label>
+                <div id="strokeHardness" class="label"></div>
+            </div>
         </div>
     </body>
 
diff --git a/js/components/tools-properties/brush-properties.reel/brush-properties.js b/js/components/tools-properties/brush-properties.reel/brush-properties.js
index 92da98cc..d2fcf888 100755
--- a/js/components/tools-properties/brush-properties.reel/brush-properties.js
+++ b/js/components/tools-properties/brush-properties.reel/brush-properties.js
@@ -9,17 +9,10 @@ var Component = require("montage/ui/component").Component;
 var ToolProperties = require("js/components/tools-properties/tool-properties").ToolProperties;
 
 exports.BrushProperties = Montage.create(ToolProperties, {
-
-    
-    _subPrepare: {
-        value: function() {
-            //this.divElement.addEventListener("click", this, false);
-        }
+    strokeSize: {
+        get: function() { return this._strokeSize; }
     },
-
-    handleClick: {
-        value: function(event) {
-           // this.selectedElement = event._event.target.id;
-        }
+    strokeHardness: {
+        get: function() { return this._strokeHardness; }
     }
-});
\ No newline at end of file
+});
diff --git a/js/helper-classes/RDGE/GLBrushStroke.js b/js/helper-classes/RDGE/GLBrushStroke.js
index f5b0bce1..c1469977 100755
--- a/js/helper-classes/RDGE/GLBrushStroke.js
+++ b/js/helper-classes/RDGE/GLBrushStroke.js
@@ -31,6 +31,8 @@ function GLBrushStroke() {
     //stroke information
     this._strokeWidth = 0.0;
     this._strokeColor = [0.4, 0.4, 0.4, 1.0];
+    this._secondStrokeColor = this._strokeColor;
+    this._strokeHardness = 100;
     this._strokeMaterial;
     this._strokeStyle = "Solid";
 
@@ -39,7 +41,7 @@ function GLBrushStroke() {
     this._WETNESS_FACTOR = 0.25;
 
     //prevent extremely long paths that can take a long time to render
-    this._MAX_ALLOWED_SAMPLES = 500;
+    this._MAX_ALLOWED_SAMPLES = 5000;
 
     //drawing context
     this._world = null;
@@ -106,6 +108,8 @@ function GLBrushStroke() {
     this.setStrokeMaterial = function (m) { this._strokeMaterial = m; }
     this.getStrokeColor = function () { return this._strokeColor; }
     this.setStrokeColor = function (c) { this._strokeColor = c; }
+    this.setSecondStrokeColor = function(c){this._secondStrokeColor=c;}
+    this.setStrokeHardness = function(h){this._strokeHardness=h;}
     this.getStrokeStyle = function () { return this._strokeStyle; }
     this.setStrokeStyle = function (s) { this._strokeStyle = s; }
 
@@ -133,7 +137,7 @@ function GLBrushStroke() {
             this._Points[i][2]+=tz;
         }
     }
-
+    
     this.computeMetaGeometry = function(){
         if (this._dirty){
             var numPoints = this._Points.length;
@@ -172,6 +176,44 @@ function GLBrushStroke() {
                 }
             }
 
+            //**** add samples to the long sections of the path --- Catmull-Rom spline interpolation
+            if (numPoints>1) {
+                var numInsertedPoints = 0;
+                var threshold = 5;//0.25*this._strokeWidth; //this determines whether a segment between two sample is too long
+                var prevPt = this._Points[0];
+                for (var i=1;i<numPoints;i++){
+                    var pt = this._Points[i];
+                    var diff = [pt[0]-prevPt[0], pt[1]-prevPt[1]];
+                    var distance = Math.sqrt(diff[0]*diff[0]+diff[1]*diff[1]);
+                    if (distance>threshold){
+                        //build the control polygon for the Catmull-Rom spline (prev. 2 points and next 2 points)
+                        var prev = (i===1) ? i-1 : i-2;
+                        var next = (i===numPoints-1) ? i : i+1;
+                        var ctrlPts = [this._Points[prev], this._Points[i-1], this._Points[i], this._Points[next]];
+                        //insert points along the prev. to current point
+                        var numNewPoints = Math.floor(distance/threshold);
+                        for (var j=0;j<numNewPoints;j++){
+                            var param = (j+1)/(numNewPoints+1);
+                            var newpt = this._CatmullRomSplineInterpolate(ctrlPts, param);
+                            //insert new point before point i
+                            this._Points.splice(i, 0, newpt);
+                            i++;
+                            numInsertedPoints++;
+                        }
+                        this._dirty=true;
+                    }
+                    prevPt=pt;
+                    //update numPoints to match the new length
+                    numPoints = this._Points.length;
+
+                    //end this function if the numPoints has gone above the max. size specified
+                    if (numPoints> this._MAX_ALLOWED_SAMPLES){
+                        console.log("leaving the resampling because numPoints is greater than limit:"+this._MAX_ALLOWED_SAMPLES);
+                        break;
+                    }
+                }
+                console.log("Inserted "+numInsertedPoints+" additional CatmullRom points");
+            }
             // *** compute the bounding box *********
             this._BBoxMin = [Infinity, Infinity, Infinity];
             this._BBoxMax = [-Infinity, -Infinity, -Infinity];
@@ -362,7 +404,6 @@ function GLBrushStroke() {
         }
         */
 
-        //todo test how to render the path as a bunch of moveTo and lineTos (for calligraphic brush styles)
         var numTraces = this._strokeWidth;
         var halfNumTraces = numTraces/2;
         var deltaDisplacement = [1,0];//[this._strokeWidth/numTraces, 0]; //a horizontal line brush
@@ -371,17 +412,20 @@ function GLBrushStroke() {
             var disp = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]];
             //ctx.globalCompositeOperation = 'source-over';
             var distFromMiddle = Math.abs(halfNumTraces-t);
-            var alphaVal = 1.0 - (distFromMiddle/halfNumTraces);
+            var alphaVal = 1.0 - (100-this._strokeHardness)*(distFromMiddle/halfNumTraces)/100;
             ctx.save();
             ctx.lineWidth=this._strokeWidth/10;//4;
             if (ctx.lineWidth<2)
                 ctx.lineWidth=2;
+            if (t===numTraces-1){
+                ctx.lineWidth = 1; 
+            }
             ctx.lineJoin="bevel";
             ctx.lineCap="butt";
             if (t<numTraces/2)
                 ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")";
             else
-                ctx.strokeStyle="rgba("+255+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")";
+                ctx.strokeStyle="rgba("+parseInt(255*this._secondStrokeColor[0])+","+parseInt(255*this._secondStrokeColor[1])+","+parseInt(255*this._secondStrokeColor[2])+","+alphaVal+")";
             ctx.translate(disp[0],disp[1]);
             ctx.beginPath();
             ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]);
@@ -437,4 +481,20 @@ function GLBrushStroke() {
         return true;
     }
 
-} //function GLSubpath ...class definition
\ No newline at end of file
+} //function GLBrushStroke ...class definition
+
+GLBrushStroke.prototype._CatmullRomSplineInterpolate = function(ctrlPts, t)
+{
+    //perform CatmullRom interpolation on the spline...assume t is in [0,1]
+    var t2 = t*t;
+    var t3 = t2*t;
+    var retPoint = [0,0,0];
+    for (var i=0;i<3;i++){
+        retPoint[i] = 0.5 *(
+            (2*ctrlPts[1][i]) +
+            (-ctrlPts[0][i] + ctrlPts[2][i]) * t +
+            (2*ctrlPts[0][i] - 5*ctrlPts[1][i] + 4*ctrlPts[2][i] - ctrlPts[3][i]) * t2 +
+            (-ctrlPts[0][i] + 3*ctrlPts[1][i]- 3*ctrlPts[2][i] + ctrlPts[3][i]) * t3);
+    }
+    return retPoint;
+}
\ No newline at end of file
diff --git a/js/tools/BrushTool.js b/js/tools/BrushTool.js
index dc9e022e..8b0f60bb 100644
--- a/js/tools/BrushTool.js
+++ b/js/tools/BrushTool.js
@@ -5,6 +5,7 @@ No rights, expressed or implied, whatsoever to this software are provided by Mot
 </copyright> */
 
 var ShapeTool = require("js/tools/ShapeTool").ShapeTool;
+var ShapesController = 	require("js/controllers/elements/shapes-controller").ShapesController;
 var DrawingToolBase = require("js/tools/drawing-tool-base").DrawingToolBase;
 var defaultEventManager = require("montage/core/event/event-manager").defaultEventManager;
 var Montage = require("montage/core/core").Montage;
@@ -67,12 +68,24 @@ exports.BrushTool = Montage.create(ShapeTool, {
                     if (this.application.ninja.colorController.colorToolbar.stroke.webGlColor){
                         this._selectedBrushStroke.setStrokeColor(this.application.ninja.colorController.colorToolbar.stroke.webGlColor);
                     }
+                    if (this.application.ninja.colorController.colorToolbar.fill.webGlColor){
+                        this._selectedBrushStroke.setSecondStrokeColor(this.application.ninja.colorController.colorToolbar.fill.webGlColor);
+                    }
                     //add this point to the brush stroke in case the user does a mouse up before doing a mouse move
                     var currMousePos = this._getUnsnappedPosition(event.pageX, event.pageY);
                     this._selectedBrushStroke.addPoint(currMousePos);
 
-                    //TODO get these values from the options
-                    this._selectedBrushStroke.setStrokeWidth(10);
+                    var strokeSize = 1;
+                    if (this.options.strokeSize) {
+                        strokeSize = ShapesController.GetValueInPixels(this.options.strokeSize.value, this.options.strokeSize.units);
+                    }
+                    this._selectedBrushStroke.setStrokeWidth(strokeSize);
+
+                    var strokeHardness = 100;
+                    if (this.options.strokeHardness){
+                        strokeHardness = ShapesController.GetValueInPixels(this.options.strokeHardness.value, this.options.strokeHardness.units);
+                    }
+                    this._selectedBrushStroke.setStrokeHardness(strokeHardness);
                 }
                 NJevent("enableStageMove");//stageManagerModule.stageManager.enableMouseMove();
             } //value: function (event) {
-- 
cgit v1.2.3


From 264e3d8e6d3624083d2fab9fe2560234553bb2ad Mon Sep 17 00:00:00 2001
From: Pushkar Joshi
Date: Tue, 6 Mar 2012 17:03:06 -0800
Subject: draw brush stroke as concentric paths overlaid on top of each
 other...allows us to simulate softness for circular brushes

---
 js/helper-classes/RDGE/GLBrushStroke.js | 135 +++++++++++++++++++++++---------
 1 file changed, 98 insertions(+), 37 deletions(-)

diff --git a/js/helper-classes/RDGE/GLBrushStroke.js b/js/helper-classes/RDGE/GLBrushStroke.js
index c1469977..26c922a3 100755
--- a/js/helper-classes/RDGE/GLBrushStroke.js
+++ b/js/helper-classes/RDGE/GLBrushStroke.js
@@ -81,7 +81,7 @@ function GLBrushStroke() {
         //add the point only if it is some epsilon away from the previous point
         var numPoints = this._Points.length;
         if (numPoints>0) {
-            var threshold = this._WETNESS_FACTOR*this._strokeWidth;
+            var threshold = 1;//this._WETNESS_FACTOR*this._strokeWidth;
             var prevPt = this._Points[numPoints-1];
             var diffPt = [prevPt[0]-pt[0], prevPt[1]-pt[1]];
             var diffPtMag = Math.sqrt(diffPt[0]*diffPt[0] + diffPt[1]*diffPt[1]);
@@ -375,46 +375,58 @@ function GLBrushStroke() {
         */
 
         /*
-        var R2 = this._strokeWidth;
-        var R = R2*0.5;
-        var hardness = 0; //for a pencil, this is always 1 //TODO get hardness parameter from user interface
-        var innerRadius = (hardness*R)-1;
-        if (innerRadius<1)
-            innerRadius=1;
-
-        var r = ctx.createRadialGradient(0,0,innerRadius, 0,0,R);
-        var midColor = "rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+",1)";
-        r.addColorStop(0, midColor);
-        var endColor = "rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+",0.0)";
-        r.addColorStop(1, endColor);
-        ctx.fillStyle = r;
-
-        for (var i = 0; i < numPoints; i++) {
-            var pt = this._Points[i];
-            ctx.globalCompositeOperation = 'source-over';
-            var x = pt[0]-bboxMin[0];
-            var y = pt[1]-bboxMin[1];
-            ctx.save();
-            ctx.translate(x,y);
-            ctx.arc(0, 0, R, 0, 2 * Math.PI, false);
-            ctx.fill();
-            ctx.restore();
-            //ctx.globalCompositeOperation = 'source-in';
-            //ctx.rect(x-R, y-R, R2, R2);
-        }
-        */
-
+        //build the stamp for the brush stroke
+        //todo get this directly from the UI
+        var t=0;
         var numTraces = this._strokeWidth;
         var halfNumTraces = numTraces/2;
-        var deltaDisplacement = [1,0];//[this._strokeWidth/numTraces, 0]; //a horizontal line brush
         var startPos = [-this._strokeWidth/2,0];
-        for (var t=0;t<numTraces;t++){
-            var disp = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]];
+        var brushStamp = [];
+
+        //build an angled (calligraphic) brush stamp
+        var deltaDisplacement = [1,1];//[this._strokeWidth/numTraces, 0]; //a horizontal line brush
+        for (t=0;t<numTraces;t++){
+            var brushPt = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]];
+            brushStamp.push(brushPt);
+        }
+
+
+        //make a circular brush stamp
+        brushStamp=[];
+        numTraces = this._strokeWidth*Math.PI; //figure out how to
+        var radius = this._strokeWidth/2;
+        for (t=0;t<numTraces;t++)
+        {
+            var angle = Math.PI*(360*t/numTraces)/180;
+            var brushPt = [radius*Math.cos(angle), radius*Math.sin(angle)];
+            brushStamp.push(brushPt);
+        }
+        
+//        //make a square brush stamp
+//        STOPPED HERE
+//        brushStamp = [];
+//        numTraces = 4*strokeWidth;
+//        for (t=0;t<numTraces;t++){
+//            if (t<numTraces*0.25){
+//                var brushPt = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]];
+//            } else if (t<numTraces*0.5){
+//
+//            } else if (t<numTraces*0.75){
+//
+//            } else {
+//
+//            }
+//            brushStamp.push(brushPt);
+//        }
+
+        for (t=0;t<numTraces;t++){
+            var disp = [brushStamp[t][0], brushStamp[t][1]];
             //ctx.globalCompositeOperation = 'source-over';
             var distFromMiddle = Math.abs(halfNumTraces-t);
             var alphaVal = 1.0 - (100-this._strokeHardness)*(distFromMiddle/halfNumTraces)/100;
+            alphaVal = 0.2;
             ctx.save();
-            ctx.lineWidth=this._strokeWidth/10;//4;
+            ctx.lineWidth=this._strokeWidth/10;//todo figure out the correct formula for the line width
             if (ctx.lineWidth<2)
                 ctx.lineWidth=2;
             if (t===numTraces-1){
@@ -422,10 +434,10 @@ function GLBrushStroke() {
             }
             ctx.lineJoin="bevel";
             ctx.lineCap="butt";
-            if (t<numTraces/2)
+            //if (t<numTraces/2)
                 ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")";
-            else
-                ctx.strokeStyle="rgba("+parseInt(255*this._secondStrokeColor[0])+","+parseInt(255*this._secondStrokeColor[1])+","+parseInt(255*this._secondStrokeColor[2])+","+alphaVal+")";
+            //else
+            //    ctx.strokeStyle="rgba("+parseInt(255*this._secondStrokeColor[0])+","+parseInt(255*this._secondStrokeColor[1])+","+parseInt(255*this._secondStrokeColor[2])+","+alphaVal+")";
             ctx.translate(disp[0],disp[1]);
             ctx.beginPath();
             ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]);
@@ -435,8 +447,10 @@ function GLBrushStroke() {
             ctx.stroke();
             ctx.restore();
         }
+        */
 
         /*
+        //debugging path
         ctx.beginPath();
         ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]);
         for (var i=1;i<numPoints;i++){
@@ -447,6 +461,53 @@ function GLBrushStroke() {
         ctx.stroke();
         */
 
+        var numlayers = this._strokeWidth/2;
+        var alphaVal = 1.0/(numlayers-1);
+        for (var l=0;l<numlayers;l++){
+            ctx.beginPath();
+            ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]);
+            for (var i=1;i<numPoints;i++){
+                ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]);
+            }
+            ctx.lineCap = "round";
+            ctx.lineJoin="round";
+            ctx.lineWidth=l+1;
+            ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")";
+            ctx.stroke();
+        }
+
+        /*
+        var R2 = this._strokeWidth;
+        var R = R2*0.5;
+        var hardness = 0; //for a pencil, this is always 1 //TODO get hardness parameter from user interface
+        var innerRadius = (hardness*R)-1;
+        if (innerRadius<1)
+            innerRadius=1;
+
+        var r = ctx.createRadialGradient(0,0,innerRadius, 0,0,R);
+        var midColor = "rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+",1)";
+        r.addColorStop(0, midColor);
+        var endColor = "rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+",0.0)";
+        r.addColorStop(1, endColor);
+        ctx.fillStyle = r;
+
+        for (var i = 0; i < numPoints; i++) {
+            var pt = this._Points[i];
+            ctx.globalCompositeOperation = 'source-over';
+            var x = pt[0]-bboxMin[0];
+            var y = pt[1]-bboxMin[1];
+            ctx.save();
+            ctx.translate(x,y);
+            ctx.arc(0, 0, R, 0, 2 * Math.PI, false);
+            ctx.fill();
+            ctx.restore();
+            break;
+            //ctx.globalCompositeOperation = 'source-in';
+            //ctx.rect(x-R, y-R, R2, R2);
+        }
+        */
+
+
         ctx.restore();
     } //render()
 
-- 
cgit v1.2.3


From 40f3fc5feae866c99af818a886e8bf9d8cf2b8dd Mon Sep 17 00:00:00 2001
From: Pushkar Joshi
Date: Tue, 6 Mar 2012 17:23:58 -0800
Subject: merge Valerio's architecture changes into pen tool

---
 js/lib/geom/sub-path.js | 84 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 84 insertions(+)

diff --git a/js/lib/geom/sub-path.js b/js/lib/geom/sub-path.js
index ab54d1e9..0a65511b 100755
--- a/js/lib/geom/sub-path.js
+++ b/js/lib/geom/sub-path.js
@@ -584,6 +584,90 @@ GLSubpath.prototype.pickAnchor = function (pickX, pickY, pickZ, radius) {
     return selAnchorIndex;
 };
 
+GLSubpath.prototype.isWithinBBox =function(x,y,z){
+    if (this._BBoxMin[0]>x || this._BBoxMin[1]>y || this._BBoxMin[2]>z){
+        return false;
+    }
+    if (this._BBoxMax[0]<x || this._BBoxMax[1]<y || this._BBoxMax[2]<z){
+        return false;
+    }
+    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
+GLSubpath.prototype.pathHitTest = function (pickX, pickY, pickZ, radius) {
+    var numAnchors = this._Anchors.length;
+    var selAnchorIndex = -1;
+    var retParam = null;
+    var radSq = radius * radius;
+    var minDistance = Infinity;
+
+    //check if the location is close to the currently selected anchor position
+    if (this._selectedAnchorIndex>=0 && this._selectedAnchorIndex<this._Anchors.length){
+        var distSq = this._Anchors[this._selectedAnchorIndex].getDistanceSq(pickX, pickY, pickZ);
+        //check the anchor point
+        if (distSq < minDistance && distSq < radSq) {
+            selAnchorIndex = this._selectedAnchorIndex;
+            minDistance = distSq;
+        }
+    }
+    //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;
+        } else {
+            //check the next for this anchor point
+            distSq = this._Anchors[this._selectedAnchorIndex].getNextDistanceSq(pickX, pickY, pickZ);
+            if (distSq<minDistance && distSq<radSq){
+                selAnchorIndex = this._selectedAnchorIndex;
+                minDistance = distSq;
+            }
+        }
+    }
+
+    //now check if the location is close to any anchor position
+    if (selAnchorIndex===-1) {
+        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;
+            }
+        }//for every anchor i
+    }
+
+    //finally check if the location is close to the curve itself
+    if (selAnchorIndex===-1) {
+        //first check if the input location is within the bounding box
+        if (this.isWithinBBox(pickX,pickY,pickZ)){
+            var numSegments = this._isClosed ? numAnchors : numAnchors-1;
+            for (var i = 0; i < numSegments; i++) {
+                var nextIndex = (i+1)%numAnchors;
+                //check if the point is close to the bezier segment between anchor i and anchor nextIndex
+                var controlPoints = [[this._Anchors[i].getPosX(),this._Anchors[i].getPosY(),this._Anchors[i].getPosZ()],
+                    [this._Anchors[i].getNextX(),this._Anchors[i].getNextY(),this._Anchors[i].getNextZ()],
+                    [this._Anchors[nextIndex].getPrevX(),this._Anchors[nextIndex].getPrevY(),this._Anchors[nextIndex].getPrevZ()],
+                    [this._Anchors[nextIndex].getPosX(),this._Anchors[nextIndex].getPosY(),this._Anchors[nextIndex].getPosZ()]];
+                var point = [pickX, pickY, pickZ];
+                if (this._isWithinBoundingBox(point, controlPoints, radius)) {
+                    //var intersectParam = this._checkIntersection(controlPoints, 0.0, 1.0, point, radius);
+                    var intersectParam = this._checkIntersectionWithSamples(this._anchorSampleIndex[i], this._anchorSampleIndex[nextIndex], point, radius);
+                    console.log("intersectParam:"+intersectParam);
+                    if (intersectParam){
+                        selAnchorIndex=i;
+                        retParam = intersectParam-i; //make the retParam go from 0 to 1
+                        break;
+                    }
+                }
+            }//for every anchor i
+        }//if is within bbox
+    }
+    return [selAnchorIndex,retParam];
+}     //GLSubpath.pathHitTest function
+
 //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
 GLSubpath.prototype.pickPath = function (pickX, pickY, pickZ, radius) {
     var numAnchors = this._Anchors.length;
-- 
cgit v1.2.3


From 353bfa04a6153b0146a0c73b6e6bb0e5f4b23194 Mon Sep 17 00:00:00 2001
From: Pushkar Joshi
Date: Tue, 6 Mar 2012 17:40:22 -0800
Subject: merge Valerio's architecture changes

---
 js/lib/geom/brush-stroke.js | 177 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 172 insertions(+), 5 deletions(-)

diff --git a/js/lib/geom/brush-stroke.js b/js/lib/geom/brush-stroke.js
index 9a934928..39af5c5c 100755
--- a/js/lib/geom/brush-stroke.js
+++ b/js/lib/geom/brush-stroke.js
@@ -32,6 +32,8 @@ var BrushStroke = function GLBrushStroke() {
     //stroke information
     this._strokeWidth = 0.0;
     this._strokeColor = [0.4, 0.4, 0.4, 1.0];
+    this._secondStrokeColor = this._strokeColor;
+    this._strokeHardness = 100;
     this._strokeMaterial = null;
     this._strokeStyle = "Solid";
 
@@ -114,7 +116,7 @@ var BrushStroke = function GLBrushStroke() {
         //add the point only if it is some epsilon away from the previous point
         var numPoints = this._Points.length;
         if (numPoints>0) {
-            var threshold = this._WETNESS_FACTOR*this._strokeWidth;
+            var threshold = 1;//this._WETNESS_FACTOR*this._strokeWidth;
             var prevPt = this._Points[numPoints-1];
             var diffPt = [prevPt[0]-pt[0], prevPt[1]-pt[1]];
             var diffPtMag = Math.sqrt(diffPt[0]*diffPt[0] + diffPt[1]*diffPt[1]);
@@ -173,6 +175,14 @@ var BrushStroke = function GLBrushStroke() {
         this._strokeColor = c;
     };
 
+    this.setSecondStrokeColor = function(c){
+        this._secondStrokeColor=c;
+    }
+
+    this.setStrokeHardness = function(h){
+        this._strokeHardness=h;
+    }
+
     this.getStrokeStyle = function () {
         return this._strokeStyle;
     };
@@ -219,7 +229,8 @@ var BrushStroke = function GLBrushStroke() {
             var numPoints = this._Points.length;
 
             //**** add samples to the path if needed...linear interpolation for now
-            if (numPoints>1) {
+            //if (numPoints>1) {
+            if (0){
                 var threshold = this._WETNESS_FACTOR*this._strokeWidth;
                 var prevPt = this._Points[0];
                 var prevIndex = 0;
@@ -250,6 +261,44 @@ var BrushStroke = function GLBrushStroke() {
                     }
                 }
             }
+            //**** add samples to the long sections of the path --- Catmull-Rom spline interpolation
+            if (numPoints>1) {
+                var numInsertedPoints = 0;
+                var threshold = 5;//0.25*this._strokeWidth; //this determines whether a segment between two sample is too long
+                var prevPt = this._Points[0];
+                for (var i=1;i<numPoints;i++){
+                    var pt = this._Points[i];
+                    var diff = [pt[0]-prevPt[0], pt[1]-prevPt[1]];
+                    var distance = Math.sqrt(diff[0]*diff[0]+diff[1]*diff[1]);
+                    if (distance>threshold){
+                        //build the control polygon for the Catmull-Rom spline (prev. 2 points and next 2 points)
+                        var prev = (i===1) ? i-1 : i-2;
+                        var next = (i===numPoints-1) ? i : i+1;
+                        var ctrlPts = [this._Points[prev], this._Points[i-1], this._Points[i], this._Points[next]];
+                        //insert points along the prev. to current point
+                        var numNewPoints = Math.floor(distance/threshold);
+                        for (var j=0;j<numNewPoints;j++){
+                            var param = (j+1)/(numNewPoints+1);
+                            var newpt = this._CatmullRomSplineInterpolate(ctrlPts, param);
+                            //insert new point before point i
+                            this._Points.splice(i, 0, newpt);
+                            i++;
+                            numInsertedPoints++;
+                        }
+                        this._dirty=true;
+                    }
+                    prevPt=pt;
+                    //update numPoints to match the new length
+                    numPoints = this._Points.length;
+
+                    //end this function if the numPoints has gone above the max. size specified
+                    if (numPoints> this._MAX_ALLOWED_SAMPLES){
+                        console.log("leaving the resampling because numPoints is greater than limit:"+this._MAX_ALLOWED_SAMPLES);
+                        break;
+                    }
+                }
+                console.log("Inserted "+numInsertedPoints+" additional CatmullRom points");
+            }
 
             // *** compute the bounding box *********
             this._BBoxMin = [Infinity, Infinity, Infinity];
@@ -412,7 +461,7 @@ var BrushStroke = function GLBrushStroke() {
         }
         */
 
-
+        /*
         var R2 = this._strokeWidth;
         var R = R2*0.5;
         var hardness = 0; //for a pencil, this is always 1 //TODO get hardness parameter from user interface
@@ -440,6 +489,109 @@ var BrushStroke = function GLBrushStroke() {
             //ctx.globalCompositeOperation = 'source-in';
             //ctx.rect(x-R, y-R, R2, R2);
         }
+        */
+
+        /*
+        //build the stamp for the brush stroke
+        //todo get this directly from the UI
+        var t=0;
+        var numTraces = this._strokeWidth;
+        var halfNumTraces = numTraces/2;
+        var startPos = [-this._strokeWidth/2,0];
+        var brushStamp = [];
+
+        //build an angled (calligraphic) brush stamp
+        var deltaDisplacement = [1,1];//[this._strokeWidth/numTraces, 0]; //a horizontal line brush
+        for (t=0;t<numTraces;t++){
+            var brushPt = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]];
+            brushStamp.push(brushPt);
+        }
+
+
+        //make a circular brush stamp
+        brushStamp=[];
+        numTraces = this._strokeWidth*Math.PI; //figure out how to
+        var radius = this._strokeWidth/2;
+        for (t=0;t<numTraces;t++)
+        {
+            var angle = Math.PI*(360*t/numTraces)/180;
+            var brushPt = [radius*Math.cos(angle), radius*Math.sin(angle)];
+            brushStamp.push(brushPt);
+        }
+
+//        //make a square brush stamp
+//        STOPPED HERE
+//        brushStamp = [];
+//        numTraces = 4*strokeWidth;
+//        for (t=0;t<numTraces;t++){
+//            if (t<numTraces*0.25){
+//                var brushPt = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]];
+//            } else if (t<numTraces*0.5){
+//
+//            } else if (t<numTraces*0.75){
+//
+//            } else {
+//
+//            }
+//            brushStamp.push(brushPt);
+//        }
+
+        for (t=0;t<numTraces;t++){
+            var disp = [brushStamp[t][0], brushStamp[t][1]];
+            //ctx.globalCompositeOperation = 'source-over';
+            var distFromMiddle = Math.abs(halfNumTraces-t);
+            var alphaVal = 1.0 - (100-this._strokeHardness)*(distFromMiddle/halfNumTraces)/100;
+            alphaVal = 0.2;
+            ctx.save();
+            ctx.lineWidth=this._strokeWidth/10;//todo figure out the correct formula for the line width
+            if (ctx.lineWidth<2)
+                ctx.lineWidth=2;
+            if (t===numTraces-1){
+                ctx.lineWidth = 1;
+            }
+            ctx.lineJoin="bevel";
+            ctx.lineCap="butt";
+            //if (t<numTraces/2)
+                ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")";
+            //else
+            //    ctx.strokeStyle="rgba("+parseInt(255*this._secondStrokeColor[0])+","+parseInt(255*this._secondStrokeColor[1])+","+parseInt(255*this._secondStrokeColor[2])+","+alphaVal+")";
+            ctx.translate(disp[0],disp[1]);
+            ctx.beginPath();
+            ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]);
+            for (var i=0;i<numPoints;i++){
+                ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]);
+            }
+            ctx.stroke();
+            ctx.restore();
+        }
+        */
+
+        /*
+        //debugging path
+        ctx.beginPath();
+        ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]);
+        for (var i=1;i<numPoints;i++){
+            ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]);
+        }
+        ctx.lineWidth=1.0;
+        ctx.strokeStyle = "black";
+        ctx.stroke();
+        */
+
+        var numlayers = this._strokeWidth/2;
+        var alphaVal = 1.0/(numlayers-1);
+        for (var l=0;l<numlayers;l++){
+            ctx.beginPath();
+            ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]);
+            for (var i=1;i<numPoints;i++){
+                ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]);
+            }
+            ctx.lineCap = "round";
+            ctx.lineJoin="round";
+            ctx.lineWidth=l+1;
+            ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")";
+            ctx.stroke();
+        }
 
         ctx.restore();
     }; //render()
@@ -473,10 +625,25 @@ var BrushStroke = function GLBrushStroke() {
         return true;
     };
 
-}; //function GLSubpath ...class definition
+}; //function BrushStroke ...class definition
 
 BrushStroke.prototype = new GeomObj();
 
+BrushStroke.prototype._CatmullRomSplineInterpolate = function(ctrlPts, t)
+{
+    //perform CatmullRom interpolation on the spline...assume t is in [0,1]
+    var t2 = t*t;
+    var t3 = t2*t;
+    var retPoint = [0,0,0];
+    for (var i=0;i<3;i++){
+        retPoint[i] = 0.5 *(
+            (2*ctrlPts[1][i]) +
+            (-ctrlPts[0][i] + ctrlPts[2][i]) * t +
+            (2*ctrlPts[0][i] - 5*ctrlPts[1][i] + 4*ctrlPts[2][i] - ctrlPts[3][i]) * t2 +
+            (-ctrlPts[0][i] + 3*ctrlPts[1][i]- 3*ctrlPts[2][i] + ctrlPts[3][i]) * t3);
+    }
+    return retPoint;
+}
 if (typeof exports === "object") {
     exports.BrushStroke = BrushStroke;
-}
\ No newline at end of file
+}
-- 
cgit v1.2.3


From 703fb3d06e88257ac73c1d1a0ec6ca33a64f4371 Mon Sep 17 00:00:00 2001
From: Pushkar Joshi
Date: Wed, 7 Mar 2012 14:33:21 -0800
Subject: implement stroke hardness such that it is percentage of the stroke
 width that's fully the color of the brush

AND
add a smoothing flag for the brush options
---
 .../brush-properties.reel/brush-properties.html    |  4 +-
 .../brush-properties.reel/brush-properties.js      |  3 +
 js/lib/geom/brush-stroke.js                        | 66 +++++++---------------
 js/tools/BrushTool.js                              |  6 ++
 4 files changed, 31 insertions(+), 48 deletions(-)

diff --git a/js/components/tools-properties/brush-properties.reel/brush-properties.html b/js/components/tools-properties/brush-properties.reel/brush-properties.html
index af07b3f2..6d4852e6 100755
--- a/js/components/tools-properties/brush-properties.reel/brush-properties.html
+++ b/js/components/tools-properties/brush-properties.reel/brush-properties.html
@@ -43,7 +43,8 @@
                 "properties": {
                     "element": {"#": "brushProperties"},
                     "_strokeSize": {"@": "strokeSizeHT"},
-                    "_strokeHardness": {"@": "strokeHardnessHT"}
+                    "_strokeHardness": {"@": "strokeHardnessHT"},
+                    "_doSmoothing": {"@": "doSmoothing"}
                 }
             }
     	}
@@ -58,6 +59,7 @@
                 <div id="strokeSize" class="label"></div>
                 <label class="label"> Hardness:</label>
                 <div id="strokeHardness" class="label"></div>
+                <label class="label subOption optionLabel"><input id="doSmoothing" type="checkbox" name="doSmoothingControl" class="checkBoxAlign"/>Smoothing</label>
             </div>
         </div>
     </body>
diff --git a/js/components/tools-properties/brush-properties.reel/brush-properties.js b/js/components/tools-properties/brush-properties.reel/brush-properties.js
index d2fcf888..0ce685b5 100755
--- a/js/components/tools-properties/brush-properties.reel/brush-properties.js
+++ b/js/components/tools-properties/brush-properties.reel/brush-properties.js
@@ -14,5 +14,8 @@ exports.BrushProperties = Montage.create(ToolProperties, {
     },
     strokeHardness: {
         get: function() { return this._strokeHardness; }
+    },
+    doSmoothing:{
+        get: function() {return this._doSmoothing; }
     }
 });
diff --git a/js/lib/geom/brush-stroke.js b/js/lib/geom/brush-stroke.js
index 39af5c5c..3e64e730 100755
--- a/js/lib/geom/brush-stroke.js
+++ b/js/lib/geom/brush-stroke.js
@@ -36,6 +36,7 @@ var BrushStroke = function GLBrushStroke() {
     this._strokeHardness = 100;
     this._strokeMaterial = null;
     this._strokeStyle = "Solid";
+    this._strokeDoSmoothing = false;
 
     //the wetness of the brush (currently this is multiplied to the square of the stroke width, but todo should be changed to not depend on stroke width entirely
     //smaller value means more samples for the path
@@ -183,6 +184,10 @@ var BrushStroke = function GLBrushStroke() {
         this._strokeHardness=h;
     }
 
+    this.setDoSmoothing = function(s){
+        this._strokeDoSmoothing = s;
+    }
+
     this.getStrokeStyle = function () {
         return this._strokeStyle;
     };
@@ -262,7 +267,7 @@ var BrushStroke = function GLBrushStroke() {
                 }
             }
             //**** add samples to the long sections of the path --- Catmull-Rom spline interpolation
-            if (numPoints>1) {
+            if (this._strokeDoSmoothing && numPoints>1) {
                 var numInsertedPoints = 0;
                 var threshold = 5;//0.25*this._strokeWidth; //this determines whether a segment between two sample is too long
                 var prevPt = this._Points[0];
@@ -507,35 +512,6 @@ var BrushStroke = function GLBrushStroke() {
             brushStamp.push(brushPt);
         }
 
-
-        //make a circular brush stamp
-        brushStamp=[];
-        numTraces = this._strokeWidth*Math.PI; //figure out how to
-        var radius = this._strokeWidth/2;
-        for (t=0;t<numTraces;t++)
-        {
-            var angle = Math.PI*(360*t/numTraces)/180;
-            var brushPt = [radius*Math.cos(angle), radius*Math.sin(angle)];
-            brushStamp.push(brushPt);
-        }
-
-//        //make a square brush stamp
-//        STOPPED HERE
-//        brushStamp = [];
-//        numTraces = 4*strokeWidth;
-//        for (t=0;t<numTraces;t++){
-//            if (t<numTraces*0.25){
-//                var brushPt = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]];
-//            } else if (t<numTraces*0.5){
-//
-//            } else if (t<numTraces*0.75){
-//
-//            } else {
-//
-//            }
-//            brushStamp.push(brushPt);
-//        }
-
         for (t=0;t<numTraces;t++){
             var disp = [brushStamp[t][0], brushStamp[t][1]];
             //ctx.globalCompositeOperation = 'source-over';
@@ -566,30 +542,26 @@ var BrushStroke = function GLBrushStroke() {
         }
         */
 
-        /*
-        //debugging path
-        ctx.beginPath();
-        ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]);
-        for (var i=1;i<numPoints;i++){
-            ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]);
-        }
-        ctx.lineWidth=1.0;
-        ctx.strokeStyle = "black";
-        ctx.stroke();
-        */
 
-        var numlayers = this._strokeWidth/2;
-        var alphaVal = 1.0/(numlayers-1);
+        var minStrokeWidth = (this._strokeHardness*this._strokeWidth)/100; //the hardness is the percentage of the stroke width that's fully opaque
+        var numlayers = 1 + (this._strokeWidth-minStrokeWidth)/2;
+        var alphaVal = 1.0/(numlayers);
+        ctx.lineCap = "round";
+        ctx.lineJoin="round";
+        ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")";
+        ctx.globalCompositeOperation = 'lighter'; //we wish to add up the colors
+        ctx.globalAlpha = this._strokeColor[3];
         for (var l=0;l<numlayers;l++){
             ctx.beginPath();
             ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]);
+            if (numPoints===1){
+               ctx.lineTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]+0.01);
+            }
             for (var i=1;i<numPoints;i++){
                 ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]);
             }
-            ctx.lineCap = "round";
-            ctx.lineJoin="round";
-            ctx.lineWidth=l+1;
-            ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")";
+
+            ctx.lineWidth=2*l+minStrokeWidth;
             ctx.stroke();
         }
 
diff --git a/js/tools/BrushTool.js b/js/tools/BrushTool.js
index dd201f33..1e1df0a4 100644
--- a/js/tools/BrushTool.js
+++ b/js/tools/BrushTool.js
@@ -88,6 +88,12 @@ exports.BrushTool = Montage.create(ShapeTool, {
                         strokeHardness = ShapesController.GetValueInPixels(this.options.strokeHardness.value, this.options.strokeHardness.units);
                     }
                     this._selectedBrushStroke.setStrokeHardness(strokeHardness);
+
+                    var doSmoothing = false;
+                    if (this.options.doSmoothing){
+                        doSmoothing = this.options.doSmoothing.value;
+                    }
+                    this._selectedBrushStroke.setDoSmoothing(doSmoothing);
                 }
                 NJevent("enableStageMove");//stageManagerModule.stageManager.enableMouseMove();
             } //value: function (event) {
-- 
cgit v1.2.3


From fcab2a14f8de302948d38f55db41e2c1c92baa34 Mon Sep 17 00:00:00 2001
From: Pushkar Joshi
Date: Thu, 8 Mar 2012 16:03:17 -0800
Subject: brush stroke options for calligraphic brush style

---
 .../brush-properties.reel/brush-properties.html    |  23 +++-
 .../brush-properties.reel/brush-properties.js      |   8 +-
 js/lib/geom/brush-stroke.js                        | 147 ++++++++++++---------
 js/tools/BrushTool.js                              |  18 ++-
 4 files changed, 129 insertions(+), 67 deletions(-)

diff --git a/js/components/tools-properties/brush-properties.reel/brush-properties.html b/js/components/tools-properties/brush-properties.reel/brush-properties.html
index 6d4852e6..d96bd1ba 100755
--- a/js/components/tools-properties/brush-properties.reel/brush-properties.html
+++ b/js/components/tools-properties/brush-properties.reel/brush-properties.html
@@ -37,6 +37,19 @@
                 }
             },
 
+            "strokeAngleHT": {
+                "module": "js/components/hottextunit.reel",
+                "name": "HotTextUnit",
+                "properties": {
+                    "element": {"#": "strokeAngle"},
+                    "minValue": -90,
+                    "maxValue": 90,
+                    "value": 0,
+                    "decimalPlace": 10,
+                    "acceptableUnits" : ["px", "pt"]
+                }
+            },
+
             "owner": {
                 "module": "js/components/tools-properties/brush-properties.reel",
                 "name": "BrushProperties",
@@ -44,7 +57,9 @@
                     "element": {"#": "brushProperties"},
                     "_strokeSize": {"@": "strokeSizeHT"},
                     "_strokeHardness": {"@": "strokeHardnessHT"},
-                    "_doSmoothing": {"@": "doSmoothing"}
+                    "_doSmoothing": {"#": "doSmoothing"},
+                    "_useCalligraphic":{"#": "useCalligraphic"},
+                    "_strokeAngle": {"@": "strokeAngleHT"}
                 }
             }
     	}
@@ -59,7 +74,11 @@
                 <div id="strokeSize" class="label"></div>
                 <