/* <copyright> This file contains proprietary software owned by Motorola Mobility, Inc.<br/> No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/> (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. </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; var NJUtils = require("js/lib/NJUtils").NJUtils; var ElementMediator = require("js/mediators/element-mediator").ElementMediator; var TagTool = require("js/tools/TagTool").TagTool; var snapManager = require("js/helper-classes/3D/snap-manager").SnapManager; var ViewUtils = require("js/helper-classes/3D/view-utils").ViewUtils; var BrushStroke = require("js/lib/geom/brush-stroke").BrushStroke; //whether or not we want the mouse move to be handled all the time (not just while drawing) inside the brush tool var g_DoBrushToolMouseMove = true; exports.BrushTool = Montage.create(ShapeTool, { hasReel: { value: false }, _toolID: { value: "brushTool" }, _imageID: { value: "brushToolImg" }, _toolImageClass: { value: "brushToolUp" }, _selectedToolImageClass: { value: "brushToolDown" }, _toolTipText: { value: "Brush Tool" }, _brushView: { value: null, writable: true }, _selectedToolClass: { value: "brushToolSpecificProperties" }, _penToolProperties: { enumerable: false, value: null, writable: true }, _parentNode: { enumerable: false, value: null, writable: true }, _toolsPropertiesContainer: { enumerable: false, value: null, writable: true }, //config options _useWebGL: {value: false, writable: false}, //view options _brushStrokeCanvas: {value: null, writable: true}, _brushStrokePlaneMat: {value: null, writable: true}, dragPlane: {value: null, writable: true}, //the current brush stroke _selectedBrushStroke: {value: null, writable: true}, ShowToolProperties: { value: function () { this._brushView = PenView.create(); this._brushView.element = document.getElementById('topPanelContainer').children[0]; this._brushView.needsDraw = true; this._brushView.addEventListener(ToolEvents.TOOL_OPTION_CHANGE, this, false); } }, /* _getUnsnappedPosition: { value: function(x,y){ var elemSnap = snapManager.elementSnapEnabled(); var gridSnap = snapManager.gridSnapEnabled(); var alignSnap = snapManager.snapAlignEnabled(); snapManager.enableElementSnap(false); snapManager.enableGridSnap(false); snapManager.enableSnapAlign(false); var point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas, new WebKitPoint(x,y)); var unsnappedpos = DrawingToolBase.getHitRecPos(snapManager.snap(point.x, point.y, false)); this.dragPlane = snapManager.getDragPlane(); snapManager.enableElementSnap(elemSnap); snapManager.enableGridSnap(gridSnap); snapManager.enableSnapAlign(alignSnap); return unsnappedpos; } }, */ //use the snap manager to build a hit record corresponding to the screen X, Y position // will use the plane of the selected path as the working plane if available, else use stage getHitRecord:{ value: function(x,y, doSnap){ var elemSnap = snapManager.elementSnapEnabled(); var gridSnap = snapManager.gridSnapEnabled(); var alignSnap = snapManager.snapAlignEnabled(); if (!doSnap){ snapManager.enableElementSnap(false); snapManager.enableGridSnap(false); snapManager.enableSnapAlign(false); } if (this._selectedSubpathCanvas){ var drawingCanvas = this._selectedSubpathCanvas; var contentPlane = ViewUtils.getUnprojectedElementPlane(drawingCanvas); snapManager.pushWorkingPlane(contentPlane); } var tmpPoint = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas, new WebKitPoint(x,y)); var hitRec = snapManager.snap(tmpPoint.x, tmpPoint.y, false); this.dragPlane = snapManager.getDragPlane(); if (this._selectedSubpathCanvas){ snapManager.popWorkingPlane(); } if (!doSnap){ snapManager.enableElementSnap(elemSnap); snapManager.enableGridSnap(gridSnap); snapManager.enableSnapAlign(alignSnap); } return hitRec; } }, HandleLeftButtonDown: { value: function (event) { //ignore any right or middle clicks if (event.button !== 0) { //NOTE: this will work on Webkit only...IE has different codes (left: 1, middle: 4, right: 2) return; } if (this._canDraw) { this._isDrawing = true; } var hitRec = this.getHitRecord(event.pageX, event.pageY, false); this._brushStrokePlaneMat = hitRec.getPlaneMatrix(); //start a new brush stroke if (this._selectedBrushStroke === null){ this._selectedBrushStroke = new BrushStroke(); var colorArray=[0,0,0,0]; var color = this.options.fill.color; if (color){ colorArray = [color.r/255, color.g/255, color.b/255, color.a]; } else { colorArray = [1,1,1,0]; } this._selectedBrushStroke.setFillColor(colorArray); //add this point to the brush stroke in case the user does a mouse up before doing a mouse move var currMousePos = hitRec.calculateStageWorldPoint(); currMousePos[0]+= snapManager.getStageWidth()*0.5; currMousePos[1]+= snapManager.getStageHeight()*0.5; this._selectedBrushStroke.addPoint(currMousePos); 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 = this.options.strokeHardness.value; } this._selectedBrushStroke.setStrokeHardness(strokeHardness); var doSmoothing = false; if (this.options.doSmoothing){ doSmoothing = this.options.doSmoothing; } this._selectedBrushStroke.setDoSmoothing(doSmoothing); if (doSmoothing){ this._selectedBrushStroke.setSmoothingAmount(this.options.smoothingAmount.value); } var useCalligraphic = false; if (this.options.useCalligraphic){ useCalligraphic = this.options.useCalligraphic; } if (useCalligraphic) { this._selectedBrushStroke.setStrokeUseCalligraphic(true); var strokeAngle = 0; if (this.options.strokeAngle){ strokeAngle= this.options.strokeAngle.value; } this._selectedBrushStroke.setStrokeAngle(Math.PI * strokeAngle/180); } else { this._selectedBrushStroke.setStrokeUseCalligraphic(false); } } if (!g_DoBrushToolMouseMove) NJevent("enableStageMove");//stageManagerModule.stageManager.enableMouseMove(); } //value: function (event) { }, //HandleLeftButtonDown HandleMouseMove: { value: function (event) { //ignore any right or middle clicks if (event.button !== 0) { //NOTE: this will work on Webkit only...IE has different codes (left: 1, middle: 4, right: 2) return; } var hitRec = this.getHitRecord(event.pageX, event.pageY, false); var currMousePos = hitRec.calculateStageWorldPoint(); currMousePos[0]+= snapManager.getStageWidth()*0.5; currMousePos[1]+= snapManager.getStageHeight()*0.5; if (this._isDrawing) { if (this._selectedBrushStroke && this._selectedBrushStroke.getNumPoints()<1000){ this._selectedBrushStroke.addPoint(currMousePos); } this.ShowCurrentBrushStrokeOnStage(); } else { this.ShowCurrentBrushIconOnStage(hitRec.getScreenPoint()); } //this.drawLastSnap(); //TODO.. is this line necessary if we're not snapping? // Required cleanup for both Draw/Feedbacks }//value: function(event) }, HandleLeftButtonUp: { value: function (event) { this.endDraw(event); this._isDrawing = false; this._hasDraw = false; //finish giving enough info. to the brush stroke if (this._selectedBrushStroke){ this._selectedBrushStroke.setPlaneMatrix(this._brushStrokePlaneMat); this._selectedBrushStroke.setPlaneMatrixInverse(glmat4.inverse(this._brushStrokePlaneMat,[])); this._selectedBrushStroke.setDragPlane(this.dragPlane); } //display the previously drawn stroke in a separate canvas this.RenderCurrentBrushStroke(); this._selectedBrushStroke = null; this._brushStrokeCanvas = null; if (!g_DoBrushToolMouseMove) NJevent("disableStageMove"); } }, ShowCurrentBrushIconOnStage:{ value: function(currMousePos) { //clear the canvas before we draw anything else this.application.ninja.stage.clearDrawingCanvas(); //display the brush icon of proper size (query the options bar) var strokeSize = 1; if (this.options.strokeSize) { strokeSize = ShapesController.GetValueInPixels(this.options.strokeSize.value, this.options.strokeSize.units); } var useCalligraphic = false; if (this.options.useCalligraphic){ useCalligraphic = this.options.useCalligraphic; } var ctx = this.application.ninja.stage.drawingContext;//stageManagerModule.stageManager.drawingContext; if (ctx === null) throw ("null drawing context in Brushtool::ShowCurrentBrushStrokeOnStage"); ctx.save(); var horizontalOffset = 0;//this.application.ninja.stage.userContentLeft; var verticalOffset = 0;//this.application.ninja.stage.userContentTop; var halfStrokeWidth = 0.5*strokeSize; ctx.beginPath(); if (!useCalligraphic) { //for round brushes, draw a circle at the current mouse position ctx.arc(currMousePos[0] + horizontalOffset, currMousePos[1]+ verticalOffset, halfStrokeWidth, 0, 2 * Math.PI, false); } else { //draw an angled stroke to represent the brush tip var strokeAngle = 0; if (this.options.strokeAngle){ strokeAngle= this.options.strokeAngle.value; } strokeAngle = Math.PI * strokeAngle/180; var deltaDisplacement = [Math.cos(strokeAngle),Math.sin(strokeAngle)]; deltaDisplacement = VecUtils.vecNormalize(2, deltaDisplacement, 1); var startPos = VecUtils.vecSubtract(2, currMousePos, [-horizontalOffset+halfStrokeWidth*deltaDisplacement[0],-verticalOffset+halfStrokeWidth*deltaDisplacement[1]]); ctx.moveTo(startPos[0], startPos[1]); var endPos = VecUtils.vecAdd(2, startPos, [strokeSize*deltaDisplacement[0], strokeSize*deltaDisplacement[1]]); ctx.lineTo(endPos[0], endPos[1]); ctx.lineWidth = 2; } ctx.strokeStyle = "black"; ctx.stroke(); ctx.restore(); } }, ShowCurrentBrushStrokeOnStage:{ value: function() { //clear the canvas before we draw anything else this.application.ninja.stage.clearDrawingCanvas(); if (this._selectedBrushStroke && this._selectedBrushStroke.getNumPoints()>0){ var ctx = this.application.ninja.stage.drawingContext;//stageManagerModule.stageManager.drawingContext; if (ctx === null) throw ("null drawing context in Brushtool::ShowCurrentBrushStrokeOnStage"); ctx.save(); /*var horizontalOffset = this.application.ninja.stage.userContentLeft; var verticalOffset = this.application.ninja.stage.userContentTop; var origX = -horizontalOffset; var origY = -verticalOffset;*/ var deltaX = -snapManager.getStageWidth()*0.5; var deltaY = -snapManager.getStageHeight()*0.5; var swToGlobalMat = ViewUtils.getStageWorldToGlobalMatrix(); this._selectedBrushStroke.drawToContext(ctx, true, deltaX, deltaY, swToGlobalMat); /* origX=0;origY=0; ctx.lineWidth = 2; var numPoints = this._selectedBrushStroke.getNumPoints(); ctx.beginPath(); var tempP = this._selectedBrushStroke.getPoint(0); tempP[0]-=snapManager.getStageWidth()*0.5; tempP[1]-=snapManager.getStageHeight()*0.5; var p = MathUtils.transformAndDivideHomogeneousPoint(tempP, swToGlobalMat); ctx.moveTo(p[0]-origX, p[1]-origY); if (numPoints===1){ //display a tiny segment as a single point ctx.lineTo(p[0]-origX, p[1]-origY+0.01); } for (var i=1;i<numPoints;i++){ tempP = this._selectedBrushStroke.getPoint(i); tempP[0]-=snapManager.getStageWidth()*0.5; tempP[1]-=snapManager.getStageHeight()*0.5; p = MathUtils.transformAndDivideHomogeneousPoint(tempP, swToGlobalMat); ctx.lineTo(p[0]-origX, p[1]-origY); } ctx.stroke(); */ ctx.restore(); } } }, RenderCurrentBrushStroke:{ value: function() { if (this._selectedBrushStroke){ //DEBUGGING /*var localData = this._selectedBrushStroke.buildLocalDataFromStageWorldCoord(); var bboxWidth = localData[1]; var bboxHeight = localData[2]; var bboxMid = localData[0];*/ this._selectedBrushStroke.init(); var bboxWidth = this._selectedBrushStroke.getWidth(); var bboxHeight = this._selectedBrushStroke.getHeight(); var bboxMid = this._selectedBrushStroke.getStageWorldCenter(); //end DEBUGGING //call render shape with the bbox width and height this.RenderShape(bboxWidth, bboxHeight, this._brushStrokePlaneMat, bboxMid, this._brushStrokeCanvas); /*this._selectedBrushStroke.computeMetaGeometry(); var bboxMin = this._selectedBrushStroke.getBBoxMin(); var bboxMax = this._selectedBrushStroke.getBBoxMax(); var bboxWidth = bboxMax[0] - bboxMin[0]; var bboxHeight = bboxMax[1] - bboxMin[1]; var bboxMid = [0.5 * (bboxMax[0] + bboxMin[0]), 0.5 * (bboxMax[1] + bboxMin[1]), 0.5 * (bboxMax[2] + bboxMin[2])]; this._selectedBrushStroke.setCanvasX(bboxMid[0]); this._selectedBrushStroke.setCanvasY(bboxMid[1]); //call render shape with the bbox width and height this.RenderShape(bboxWidth, bboxHeight, this._brushStrokePlaneMat, bboxMid, this._brushStrokeCanvas); */ } } }, RenderShape: { value: function (w, h, planeMat, midPt, canvas) { if ((Math.floor(w) === 0) || (Math.floor(h) === 0)) { return; } if (!canvas) { var newCanvas = document.application.njUtils.make("canvas", {"data-RDGE-id": NJUtils.generateRandom()}, this.application.ninja.currentDocument); var styles = document.application.njUtils.stylesFromDraw(newCanvas, w, h, {midPt: midPt, planeMat: planeMat}); this.application.ninja.elementMediator.addElements(newCanvas, styles, false); // create all the GL stuff var world = this.getGLWorld(newCanvas, this._useWebGL); //store a reference to this newly created canvas this._brushStrokeCanvas = newCanvas; var brushStroke = this._selectedBrushStroke; if (brushStroke){ brushStroke.setWorld(world); brushStroke.setCanvas(newCanvas); brushStroke.setPlaneMatrix(planeMat); var planeMatInv = glmat4.inverse( planeMat, [] ); brushStroke.setPlaneMatrixInverse(planeMatInv); brushStroke.setPlaneCenter(midPt); world.addObject(brushStroke); world.render(); //TODO this will not work if there are multiple shapes in the same canvas newCanvas.elementModel.shapeModel.GLGeomObj = brushStroke; newCanvas.elementModel.shapeModel.shapeCount++; if(newCanvas.elementModel.shapeModel.shapeCount === 1) { newCanvas.elementModel.selection = "BrushStroke"; newCanvas.elementModel.pi = "BrushStrokePi"; newCanvas.elementModel.shapeModel.strokeSize = this.options.strokeSize.value + " " + this.options.strokeSize.units; newCanvas.elementModel.shapeModel.GLGeomObj = brushStroke; newCanvas.elementModel.shapeModel.useWebGl = this.options.use3D; } else { // TODO - update the shape's info only. shapeModel will likely need an array of shapes. } //now send the event that will add this canvas to the timeline NJevent("elementAdded", newCanvas); //if(newCanvas.elementModel.isShape) if (true) { this.application.ninja.selectionController.selectElement(newCanvas); } } } //if (!canvas) { else { /* var world = null; if (canvas.elementModel.shapeModel && canvas.elementModel.shapeModel.GLWorld) { world = canvas.elementModel.shapeModel.GLWorld; } else { world = this.getGLWorld(canvas, this._useWebGL); } if (this._entryEditMode !== this.ENTRY_SELECT_CANVAS){ //update the left and top of the canvas element var canvasArray=[canvas]; ElementMediator.setProperty(canvasArray, "left", [parseInt(left)+"px"],"Changing", "brushTool"); ElementMediator.setProperty(canvasArray, "top", [parseInt(top) + "px"],"Changing", "brushTool"); canvas.width = w; canvas.height = h; //update the viewport and projection to reflect the new canvas width and height world.setViewportFromCanvas(canvas); if (this._useWebGL){ var cam = world.renderer.cameraManager().getActiveCamera(); cam.setPerspective(world.getFOV(), world.getAspect(), world.getZNear(), world.getZFar()); } } var brushStroke = this._selectedBrushStroke; if (brushStroke){ brushStroke.setDrawingTool(this); brushStroke.setPlaneMatrix(planeMat); var planeMatInv = glmat4.inverse( planeMat, [] ); brushStroke.setPlaneMatrixInverse(planeMatInv); brushStroke.setPlaneCenter(midPt); brushStroke.setWorld(world); world.addIfNewObject(brushStroke); //world.addObject(subpath); world.render(); //TODO this will not work if there are multiple shapes in the same canvas canvas.elementModel.shapeModel.GLGeomObj = brushStroke; } */ alert("BrushStroke cannot edit existing canvas"); } //else of if (!canvas) { } //value: function (w, h, planeMat, midPt, canvas) { }, //RenderShape: { Configure: { value: function (wasSelected) { if (wasSelected) { if (g_DoBrushToolMouseMove){ NJevent("enableStageMove"); } } else { if (g_DoBrushToolMouseMove){ NJevent("disbleStageMove"); } } } } });