/*
This file contains proprietary software owned by Motorola Mobility, Inc.
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
(c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved.
*/
var VecUtils = require("js/helper-classes/3D/vec-utils").VecUtils;
var GeomObj = require("js/lib/geom/geom-obj").GeomObj;
var CanvasController = require("js/controllers/elements/canvas-controller").CanvasController;
// Todo: This entire class should be converted to a module
///////////////////////////////////////////////////////////////////////
// Class GLBrushStroke
// representation a sequence points (polyline) created by brush tool.
// Derived from class GLGeomObj
///////////////////////////////////////////////////////////////////////
var BrushStroke = function GLBrushStroke() {
///////////////////////////////////////////////////
// Instance variables
///////////////////////////////////////////////////
this._Points = [];
this._OrigPoints = [];
this._BBoxMin = [0, 0, 0];
this._BBoxMax = [0, 0, 0];
this._dirty = true;
this._isInit = false;
this._addedSamples = false;
this._storedOrigPoints = false;
//whether or not to use the canvas drawing to stroke/fill
this._useCanvasDrawing = true;
//the HTML5 canvas that holds this brush stroke
this._canvas = null;
//the X and Y location of this brush stroke canvas in stage world space of Ninja
this._canvasX = 0;
this._canvasY = 0;
//stroke information
this._strokeWidth = 0.0;
this._strokeColor = [0.4, 0.4, 0.4, 1.0];
this._secondStrokeColor = [1, 0.4, 0.4, 1.0];
this._strokeHardness = 100;
this._strokeMaterial = null;
this._strokeStyle = "Solid";
this._strokeDoSmoothing = false;
this._strokeUseCalligraphic = false;
this._strokeAngle = 0;
this._strokeAmountSmoothing = 0;
//threshold that tells us whether two samples are too far apart
this._MAX_SAMPLE_DISTANCE_THRESHOLD = 5;
//threshold that tells us whether two samples are too close
this._MIN_SAMPLE_DISTANCE_THRESHOLD = 2;
//prevent extremely long paths that can take a long time to render
this._MAX_ALLOWED_SAMPLES = 500;
//drawing context
this._world = null;
//tool that owns this brushstroke
this._drawingTool = null;
this._planeMat = null;
this._planeMatInv = null;
this._planeCenter = null;
/////////////////////////////////////////////////////////
// Property Accessors/Setters
/////////////////////////////////////////////////////////
this.setCanvas = function(c) {
this._canvas = c;
}
this.setWorld = function (world) {
this._world = world;
};
this.getWorld = function () {
return this._world;
};
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.getNumPoints = function () {
return this._Points.length;
};
this.getPoint = function (index) {
return this._Points[index];
};
this.addPoint = function (pt) {
//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._MIN_SAMPLE_DISTANCE_THRESHOLD;
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]);
if (diffPtMag>threshold){
this._Points.push(pt);
this._dirty=true;
}
} else {
this._Points.push(pt);
this._dirty=true;
}
};
this.insertPoint = function(pt, index){
this._Points.splice(index, 0, pt);
this._dirty=true;
};
this.isDirty = function(){
return this._dirty;
};
this.makeDirty = function(){
this._dirty=true;
};
this.getBBoxMin = function () {
return this._BBoxMin;
};
this.getBBoxMax = function () {
return this._BBoxMax;
};
this.getStrokeWidth = function () {
return this._strokeWidth;
};
this.setStrokeWidth = function (w) {
this._strokeWidth = w;
this._dirty=true;
};
this.getStrokeMaterial = function () {
return this._strokeMaterial;
};
this.setStrokeMaterial = function (m) {
this._strokeMaterial = m; this._dirty = true;
};
this.getStrokeColor = function () {
return this._strokeColor;
};
this.setStrokeColor = function (c) {
this._strokeColor = c; this._dirty = true;
};
this.setSecondStrokeColor = function(c){
this._secondStrokeColor=c; this._dirty = true;
}
this.setStrokeHardness = function(h){
if (this._strokeHardness!==h){
this._strokeHardness=h;
this._dirty = true;
}
}
this.getStrokeHardness = function(){
return this._strokeHardness;
}
this.setDoSmoothing = function(s){
if (this._strokeDoSmoothing!==s) {
this._strokeDoSmoothing = s;
this._dirty = true;
}
}
this.getDoSmoothing = function(){
return this._strokeDoSmoothing;
}
this.setSmoothingAmount = function(a){
if (this._strokeAmountSmoothing!==a) {
this._strokeAmountSmoothing = a;
this._dirty = true;
}
}
this.getSmoothingAmount = function(){
return this._strokeAmountSmoothing;
}
this.setStrokeUseCalligraphic = function(c){
if (this._strokeUseCalligraphic!==c){
this._strokeUseCalligraphic = c;
this._dirty = true;
}
}
this.setStrokeAngle = function(a){
if (this._strokeAngle!==a){
this._strokeAngle = a;
this._dirty = true;
};
}
this.getStrokeUseCalligraphic = function(){
return this._strokeUseCalligraphic;
}
this.getStrokeAngle = function(){
return this._strokeAngle;
}
this.getStrokeStyle = function () {
return this._strokeStyle;
};
this.setStrokeStyle = function (s) {
this._strokeStyle = s;
};
this.setWidth = function () {
};//NO-OP for now
this.setHeight = function () {
};//NO-OP for now
//remove all the points
this.clear = function () {
this._Points = [];
this._OrigPoints = [];
this._dirty=true;
}
/*this.translate = function (tx, ty, tz) {
for (var i=0;ithreshold){
//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 this._MAX_ALLOWED_SAMPLES){
// console.log("leaving the resampling because numPoints is greater than limit:"+this._MAX_ALLOWED_SAMPLES);
// break;
//}
}
this._Points = newSampledPoints.slice(0);
newSampledPoints = [];
this._dirty=true;
//store the original points
this._OrigPoints = this._Points.slice(0);
this._isInit = true;
} //if have not initialized this brush stroke yet
if (this._dirty) {
this._Points = this._OrigPoints.slice(0);
numPoints = this._Points.length;
if (this._strokeDoSmoothing && numPoints>1) {
//iterations of Laplacian smoothing (setting the points to the average of their neighbors)
var numLaplacianIterations = this._strokeAmountSmoothing;
for (var n=0;n pt[d]) {
this._BBoxMin[d] = pt[d];
}
if (this._BBoxMax[d] < pt[d]) {
this._BBoxMax[d] = pt[d];
}
}//for every dimension d from 0 to 2
}
}
//increase the bbox given the stroke width and the angle (in case of calligraphic brush)
var bboxPadding = this._strokeWidth/2;
//if (this._strokeUseCalligraphic) {
//todo re-enable this if check once we are able to change the left and top of the brush canvas
if (false){
this._BBoxMin[0]-= bboxPadding*Math.cos(this._strokeAngle);
this._BBoxMin[1]-= bboxPadding*Math.sin(this._strokeAngle);
this._BBoxMax[0]+= bboxPadding*Math.cos(this._strokeAngle);
this._BBoxMax[1]+= bboxPadding*Math.sin(this._strokeAngle);
} else {
for (var d = 0; d < 3; d++) {
this._BBoxMin[d]-= bboxPadding;
this._BBoxMax[d]+= bboxPadding;
}//for every dimension d from 0 to 2
}
} //if this was dirty
this._dirty = false;
};
this.buildBuffers = function () {
//return; //no need to do anything for now
};//buildBuffers()
//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 brushstroke render" );
}
var numPoints = this.getNumPoints();
if (numPoints === 0) {
return; //nothing to do for empty paths
}
this.computeMetaGeometry();
var bboxMin = this.getBBoxMin();
var bboxMax = this.getBBoxMax();
var bboxWidth = bboxMax[0] - bboxMin[0];
var bboxHeight = bboxMax[1] - bboxMin[1];
if (this._canvas) {
// this seems to produce drift as the stroke size is changed smoothly...bug due to floating point round off
//get the old left, top, width, and height
// var oldLeft = parseInt(CanvasController.getProperty(this._canvas, "left"));
// var oldTop = parseInt(CanvasController.getProperty(this._canvas, "top"));
// var oldWidth = parseInt(CanvasController.getProperty(this._canvas, "width"));
// var oldHeight = parseInt(CanvasController.getProperty(this._canvas, "height"));
// var newLeft = oldLeft - parseInt((bboxWidth-oldWidth)*0.5);
// var newTop = oldTop - parseInt((bboxHeight-oldHeight)*0.5);
//
// //assign the new width and height as the canvas dimensions through the canvas controller
// CanvasController.setProperty(this._canvas, "left", newLeft+"px");
// CanvasController.setProperty(this._canvas, "top", newTop+"px");*/
CanvasController.setProperty(this._canvas, "width", bboxWidth+"px");
CanvasController.setProperty(this._canvas, "height", bboxHeight+"px");
this._canvas.elementModel.shapeModel.GLWorld.setViewportFromCanvas(this._canvas);
}
//get the context
var ctx = world.get2DContext();
if (!ctx) {
throw ("null context in brushstroke render");
}
ctx.save();
ctx.clearRect(0, 0, bboxWidth, bboxHeight);
this.drawToContext(ctx, bboxMin[0], bboxMin[1]);
ctx.restore();
} //this.render()
this.drawToContext = function(ctx, origX, origY){
var numPoints = this.getNumPoints();
if (this._strokeUseCalligraphic) {
//build the stamp for the brush stroke
var t=0;
var numTraces = this._strokeWidth;
var halfNumTraces = numTraces/2;
var opaqueRegionHalfWidth = 0.5*this._strokeHardness*numTraces*0.01; //the 0.01 is to convert the strokeHardness from [0,100] to [0,1]
var maxTransparentRegionHalfWidth = halfNumTraces-opaqueRegionHalfWidth;
//build an angled (calligraphic) brush stamp
var deltaDisplacement = [Math.cos(this._strokeAngle),Math.sin(this._strokeAngle)];
deltaDisplacement = VecUtils.vecNormalize(2, deltaDisplacement, 1);
var startPos = [-halfNumTraces*deltaDisplacement[0],-halfNumTraces*deltaDisplacement[1]];
var brushStamp = [];
for (t=0;t0) {
alphaVal = 1.0 - distFromOpaqueRegion/maxTransparentRegionHalfWidth;
alphaVal *= 1.0/ctx.lineWidth; //factor that accounts for lineWidth !== 1
}
ctx.save();
ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")";
//linearly interpolate between the two stroke colors
var currStrokeColor = VecUtils.vecInterpolate(4, this._strokeColor, this._secondStrokeColor, t/numTraces);
//ctx.strokeStyle="rgba("+parseInt(255*currStrokeColor[0])+","+parseInt(255*currStrokeColor[1])+","+parseInt(255*currStrokeColor[2])+","+alphaVal+")";
ctx.translate(disp[0],disp[1]);
ctx.beginPath();
ctx.moveTo(this._Points[0][0]-origX, this._Points[0][1]-origY);
for (var i=0;i this._BBoxMax[0]) return false;
if (y < this._BBoxMin[1]) return false;
if (y > this._BBoxMax[1]) return false;
if (z < this._BBoxMin[2]) return false;
if (z > this._BBoxMax[2]) return false;
return true;
};
this.collidesWithPoint = function (x, y) {
if (x < this._BBoxMin[0]) return false;
if (x > this._BBoxMax[0]) return false;
if (y < this._BBoxMin[1]) return false;
if (y > this._BBoxMax[1]) return false;
return true;
};
}; //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;
}