/* 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 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 ElementController = require("js/controllers/elements/element-controller").ElementController; var snapManager = require("js/helper-classes/3D/snap-manager").SnapManager; var AnchorPoint = require("js/lib/geom/anchor-point").AnchorPoint; var SubPath = require("js/lib/geom/sub-path").SubPath; //todo remove this global var var g_DoPenToolMouseMove = true; exports.PenTool = Montage.create(ShapeTool, { _toolID: { value: "penTool" }, _imageID: { value: "penToolImg" }, _toolImageClass: { value: "penToolUp" }, _selectedToolImageClass: { value: "penToolDown" }, _toolTipText: { value: "Pen Tool" }, _penView: { value: null, writable: true }, _selectedToolClass: { value: "penToolSpecificProperties" }, _penToolProperties: { enumerable: false, value: null, writable: true }, _parentNode: { enumerable: false, value: null, writable: true }, _toolsPropertiesContainer: { enumerable: false, value: null, writable: true }, // Need to keep track of current mouse position for KEY modifiers event which do not have mouse coordinates _currentX: { value: 0, writable: true }, _currentY: { value: 0, writable: true }, //the subpaths are what is displayed on the screen currently, with _selectedSubpath being the active one currently being edited _selectedSubpath: { value: null, writable: true }, _makeMultipleSubpaths: { value: true, writable: true }, //set this to true if you want to keep making subpaths after closing current subpath //whether the user has held down the Alt key _isAltDown: { value: false, writable: true }, //whether the user has held down the Esc key _isEscapeDown: {value: false, writable: true }, //whether we have just started a new path (set true in mousedown, and set false in mouse up _isNewPath: {value: false, writable: true}, //whether we have clicked one of the endpoints after entering the pen tool in ENTRY_SELECT_PATH edit mode _isPickedEndPointInSelectPathMode: {value: false, writable: true}, //when the user wants to place a selected anchor point on top of another point, this is the target where the point will be placed _snapTarget: { value: null, writable: true }, //whether or not we're using webgl for drawing _useWebGL: {value: false, writable: false }, //the canvas created by the pen tool...this is grown or shrunk with the path (if the canvas was not already provided) _penCanvas: { value: null, writable: true }, //the plane matrix for the first click...so the entire path is on the same plane _penPlaneMat: { value: null, writable: true }, //index of the anchor point that the user has hovered over _hoveredAnchorIndex: {value: -1, writable: true}, //constants used for picking points --- NOTE: these should be user-settable parameters _PICK_POINT_RADIUS: { value: 10, writable: false }, _DISPLAY_ANCHOR_RADIUS: { value: 5, writable: false }, _DISPLAY_SELECTED_ANCHOR_RADIUS: { value: 10, writable: false }, _DISPLAY_SELECTED_ANCHOR_PREV_RADIUS: { value: 2, writable: false }, _DISPLAY_SELECTED_ANCHOR_NEXT_RADIUS: { value: 2, writable: false }, //constants used for editing modes (can be OR-ed) EDIT_NONE: { value: 0, writable: false }, EDIT_ANCHOR: { value: 1, writable: false }, EDIT_PREV: { value: 2, writable: false }, EDIT_NEXT: { value: 4, writable: false }, EDIT_PREV_NEXT: { value: 8, writable: false }, _editMode: { value: this.EDIT_NONE, writable: true }, //constants used for selection modes on entry to pen tool (mutually exclusive) ENTRY_SELECT_NONE: { value: 0, writable: false}, ENTRY_SELECT_CANVAS: { value: 1, writable: false}, ENTRY_SELECT_PATH: { value: 2, writable: false}, _entryEditMode: {value: this.ENTRY_SELECT_NONE, writable: true}, _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)); snapManager.enableElementSnap(elemSnap); snapManager.enableGridSnap(gridSnap); snapManager.enableSnapAlign(alignSnap); return unsnappedpos; } }, ShowToolProperties: { value: function () { this._penView = PenView.create(); this._penView.element = document.getElementById('topPanelContainer').children[0]; this._penView.needsDraw = true; this._penView.addEventListener(ToolEvents.TOOL_OPTION_CHANGE, this, false); } }, 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; } this.startDraw(event); //assume we are not starting a new path as we will set this to true if we create a new GLSubpath() this._isNewPath = false; //add an anchor point by computing position of mouse down var mouseDownPos = this._getUnsnappedPosition(event.pageX, event.pageY); //this.getMouseDownPos(); if (mouseDownPos) { //if we had closed the selected subpath previously, or if we have not yet started anything, create a subpath if (this._selectedSubpath === null) { this._selectedSubpath = new SubPath(); this._isNewPath = true; if (this._entryEditMode === this.ENTRY_SELECT_PATH){ //this should not happen, as ENTRY_SELECT_PATH implies there was a selected subpath this._entryEditMode = this.ENTRY_SELECT_NONE; console.log("Warning...PenTool handleMouseDown: changing from SELECT_PATH to SELECT_NONE"); } } var prevSelectedAnchorIndex = this._selectedSubpath.getSelectedAnchorIndex(); // ************* Add/Select Anchor Point ************* //check if the clicked location is close to an anchor point...if so, make that anchor the selected point and do nothing else // BUT if the anchor point selected is the first anchor point, check if the previous selected anchor was the last anchor point...in that case, close the path var selParam = this._selectedSubpath.pickPath(mouseDownPos[0], mouseDownPos[1], mouseDownPos[2], this._PICK_POINT_RADIUS); var whichPoint = this._selectedSubpath.getSelectedMode(); if (whichPoint & this._selectedSubpath.SEL_ANCHOR) { //if we're in ENTRY_SELECT_PATH mode AND we have not yet clicked on the endpoint AND if we have now clicked on the endpoint if (this._entryEditMode === this.ENTRY_SELECT_PATH && this._isPickedEndPointInSelectPathMode === false){ var selAnchorIndex = this._selectedSubpath.getSelectedAnchorIndex(); if (selAnchorIndex===0 || selAnchorIndex===this._selectedSubpath.getNumAnchors()-1){ //we have picked the endpoint of this path...reverse the path if necessary if (selAnchorIndex ===0){ //reverse this path this._selectedSubpath.reversePath(); selAnchorIndex = this._selectedSubpath.getSelectedAnchorIndex(); } this._isPickedEndPointInSelectPathMode = true; } } this._editMode = this.EDIT_ANCHOR; //if we have selected the first anchor point, and previously had selected the last anchor point, close the path var numAnchors = this._selectedSubpath.getNumAnchors(); if (numAnchors>1 && !this._selectedSubpath.getIsClosed() && this._selectedSubpath.getSelectedAnchorIndex()===0 && prevSelectedAnchorIndex === numAnchors-1){ //setting the selection mode to NONE will effectively add a new anchor point at the click location and also give us snapping whichPoint = this._selectedSubpath.SEL_NONE; //set the snap target in case the mouse move handler doesn't get called this._snapTarget = this._selectedSubpath.getAnchor(0); } } //check if the clicked location is close to prev and next of the selected anchor point..if so select that anchor, set mode to PREV or NEXT and do nothing else // but if the selectedAnchor index is not -1 and neither prev nor next are selected, it means click selected a point selParam along bezier segment starting at selectedAnchor else if (this._selectedSubpath.getSelectedAnchorIndex() !== -1) { if (whichPoint & this._selectedSubpath.SEL_PREV){ this._editMode = this.EDIT_PREV; } else if (whichPoint & this._selectedSubpath.SEL_NEXT){ this._editMode = this.EDIT_NEXT; } else if (whichPoint & this._selectedSubpath.SEL_PATH) { //the click point is close enough to insert point in bezier segment after selected anchor at selParam if (selParam > 0 && selParam < 1) { this._selectedSubpath.insertAnchorAtParameter(this._selectedSubpath.getSelectedAnchorIndex(), selParam); //set the mode so that dragging will update anchor point positions //this._editMode = this.EDIT_ANCHOR; } } } //the clicked location is not close to the path or any anchor point if (whichPoint === this._selectedSubpath.SEL_NONE) { if (this._entryEditMode !== this.ENTRY_SELECT_PATH) { //since we're not in ENTRY_SELECT_PATH mode, we don't edit the closed path...we start a new path if we clicked far away from selected path if (this._selectedSubpath.getIsClosed() && this._makeMultipleSubpaths) { this._penCanvas = null; this._penPlaneMat = null; this._snapTarget = null; this._selectedSubpath = new SubPath(); this._isNewPath = true; } //add an anchor point to end of the subpath, and make it the selected anchor point if (!this._selectedSubpath.getIsClosed() || this._makeMultipleSubpaths) { this._selectedSubpath.addAnchor(new AnchorPoint()); var newAnchor = this._selectedSubpath.getAnchor(this._selectedSubpath.getSelectedAnchorIndex()); newAnchor.setPos(mouseDownPos[0], mouseDownPos[1], mouseDownPos[2]); newAnchor.setPrevPos(mouseDownPos[0], mouseDownPos[1], mouseDownPos[2]); newAnchor.setNextPos(mouseDownPos[0], mouseDownPos[1], mouseDownPos[2]); //set the mode so that dragging will update the next and previous locations this._editMode = this.EDIT_PREV_NEXT; } } else { if (this._isPickedEndPointInSelectPathMode){ //TODO clean up this code...very similar to the code block above if (!this._selectedSubpath.getIsClosed()) { this._selectedSubpath.addAnchor(new AnchorPoint()); var newAnchor = this._selectedSubpath.getAnchor(this._selectedSubpath.getSelectedAnchorIndex()); newAnchor.setPos(mouseDownPos[0], mouseDownPos[1], mouseDownPos[2]); newAnchor.setPrevPos(mouseDownPos[0], mouseDownPos[1], mouseDownPos[2]); newAnchor.setNextPos(mouseDownPos[0], mouseDownPos[1], mouseDownPos[2]); //set the mode so that dragging will update the next and previous locations this._editMode = this.EDIT_PREV_NEXT; } } } } //if (whichPoint === this._selectedSubpath.SEL_NONE) (i.e. no anchor point was selected) //display the curve overlay this.DrawSubpathAnchors(this._selectedSubpath); this.DrawSubpathSVG(this._selectedSubpath); } //if (mouseDownPos) { i.e. if mouse down yielded a valid position if (!g_DoPenToolMouseMove){ NJevent("enableStageMove"); } this._hoveredAnchorIndex = -1; } //value: function (event) { }, //HandleLeftButtonDown //need to override this function because the ShapeTool's definition contains a clearDrawingCanvas call - Pushkar // might not need to override once we draw using OpenGL instead of SVG // Also took out all the snapping code for now...need to add that back 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; } //clear the canvas before we draw anything else 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 var currMousePos = DrawingToolBase.getHitRecPos(DrawingToolBase.getUpdatedSnapPoint(point.x, point.y, false, this.mouseDownHitRec)); if (currMousePos && this._selectedSubpath && (this._selectedSubpath.getSelectedAnchorIndex() >= 0 && this._selectedSubpath.getSelectedAnchorIndex() < this._selectedSubpath.getNumAnchors())) { //var scoord = this._getScreenCoord(this._mouseUpHitRec); var selAnchor = this._selectedSubpath.getAnchor(this._selectedSubpath.getSelectedAnchorIndex()); var selX = selAnchor.getPosX(); var selY = selAnchor.getPosY(); var selZ = selAnchor.getPosZ(); if (this._editMode & this.EDIT_ANCHOR) { selAnchor.translateAll(currMousePos[0] - selX, currMousePos[1] - selY, currMousePos[2] - selZ); } else if (this._editMode & this.EDIT_PREV) { var oldPX = selAnchor.getPrevX(); var oldPY = selAnchor.getPrevY(); var oldPZ = selAnchor.getPrevZ(); selAnchor.translatePrev(currMousePos[0] - oldPX, currMousePos[1] - oldPY, currMousePos[2] - oldPZ); //move the next point if Alt key is down to ensure relative angle between prev and next if (this._isAltDown) { selAnchor.translateNextFromPrev(currMousePos[0] - oldPX, currMousePos[1] - oldPY, currMousePos[2] - oldPZ); } } else if (this._editMode & this.EDIT_NEXT) { var oldNX = selAnchor.getNextX(); var oldNY = selAnchor.getNextY(); var oldNZ = selAnchor.getNextZ(); selAnchor.translateNext(currMousePos[0] - oldNX, currMousePos[1] - oldNY, currMousePos[2] - oldNZ); //move the prev point if Alt key is down to ensure relative angle between prev and next if (this._isAltDown) { selAnchor.translatePrevFromNext(currMousePos[0] - oldNX, currMousePos[1] - oldNY, currMousePos[2] - oldNZ); } } else if (this._editMode & this.EDIT_PREV_NEXT) { selAnchor.setNextPos(currMousePos[0], currMousePos[1], currMousePos[2]); selAnchor.setPrevFromNext(); } //snapping...check if the new location of the anchor point is close to another anchor point this._snapTarget = null; var numAnchors = this._selectedSubpath.getNumAnchors(); for (var i = 0; i < numAnchors; i++) { //check if the selected anchor is close to any other anchors if (i === this._selectedSubpath.getSelectedAnchorIndex()) continue; var currAnchor = this._selectedSubpath.getAnchor(i); var distSq = currAnchor.getDistanceSq(selX, selY, selZ); if (distSq < this._PICK_POINT_RADIUS * this._PICK_POINT_RADIUS) { //set the snap target to the location of the first close-enough anchor this._snapTarget = currAnchor; break; } } //make the subpath dirty so it will get re-drawn this._selectedSubpath.makeDirty(); this.DrawSubpathSVG(this._selectedSubpath); } } else { //if mouse is not down: //this.doSnap(event); //this.DrawHandles(); var currMousePos = this._getUnsnappedPosition(event.pageX, event.pageY); if (currMousePos && this._selectedSubpath ){ 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) { //this.drawLastSnap(); // Required cleanup for both Draw/Feedbacks if (this._selectedSubpath){ this.DrawSubpathAnchors(this._selectedSubpath); } }//value: function(event) },//HandleMouseMove TranslateSelectedSubpathPerPenCanvas:{ value: function() { if (this._penCanvas!==null) { //obtain the 2D translation of the canvas due to the Selection tool...assuming this is called in Configure var penCanvasLeft = parseInt(ElementMediator.getProperty(this._penCanvas, "left"));//parseFloat(DocumentControllerModule.DocumentController.GetElementStyle(this._penCanvas, "left")); var penCanvasTop = parseInt(ElementMediator.getProperty(this._penCanvas, "top"));//parseFloat(DocumentControllerModule.DocumentController.GetElementStyle(this._penCanvas, "top")); var penCanvasWidth = parseInt(ElementMediator.getProperty(this._penCanvas, "width"));//this._penCanvas.width; var penCanvasHeight = parseInt(ElementMediator.getProperty(this._penCanvas, "height"));//this._penCanvas.height; var penCanvasOldX = penCanvasLeft + 0.5 * penCanvasWidth; var penCanvasOldY = penCanvasTop + 0.5 * penCanvasHeight; var translateCanvasX = penCanvasOldX - this._selectedSubpath.getCanvasX(); var translateCanvasY = penCanvasOldY - this._selectedSubpath.getCanvasY(); //update the canvasX and canvasY parameters for this subpath and also translate the subpath points (since they're stored in stage world space) this._selectedSubpath.setCanvasX(translateCanvasX + this._selectedSubpath.getCanvasX()); this._selectedSubpath.setCanvasY(translateCanvasY + this._selectedSubpath.getCanvasY()); this._selectedSubpath.translateAnchors(translateCanvasX, translateCanvasY, 0); this._selectedSubpath.createSamples(); //updates the bounding box } } }, ShowSelectedSubpath:{ value: function() { if (this._selectedSubpath){ this._selectedSubpath.createSamples(); //dirty bit is checked here var bboxMin = this._selectedSubpath.getBBoxMin(); var bboxMax = this._selectedSubpath.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._selectedSubpath.setCanvasX(bboxMid[0]); this._selectedSubpath.setCanvasY(bboxMid[1]); //call render shape with the bbox width and height this.RenderShape(bboxWidth, bboxHeight, this._penPlaneMat, bboxMid, this._penCanvas); } } }, HandleLeftButtonUp: { value: function (event) { if (this._isAltDown) { var point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas, new WebKitPoint(event.pageX, event.pageY)); this.mouseUpHitRec = DrawingToolBase.getUpdatedSnapPoint(point.x, point.y, false, this.mouseDownHitRec); } else if (this._isDrawing) { this.doDraw(event); //needed to get the mouse up point in case there was no mouse move } //snapping...if there was a snapTarget and a selected anchor, move the anchor to the snap target if (this._snapTarget !== null && this._selectedSubpath && this._selectedSubpath.getSelectedAnchorIndex() !== -1) { var selAnchor = this._selectedSubpath.getAnchor(this._selectedSubpath.getSelectedAnchorIndex()); selAnchor.setPos(this._snapTarget.getPosX(), this._snapTarget.getPosY(), this._snapTarget.getPosZ()); this._selectedSubpath.makeDirty(); //if the first or last anchor point were snapped for an open path, check if the first and last anchor point are at the same position if (!this._selectedSubpath.getIsClosed()) { var lastAnchorIndex = this._selectedSubpath.getNumAnchors() - 1; var firstAnchor = this._selectedSubpath.getAnchor(0); var lastAnchor = this._selectedSubpath.getAnchor(lastAnchorIndex); if ((this._selectedSubpath.getSelectedAnchorIndex() === 0 && this._snapTarget === lastAnchor) || (this._selectedSubpath.getSelectedAnchorIndex() === lastAnchorIndex && this._snapTarget === firstAnchor)) { this._selectedSubpath.setIsClosed(true); //set the prev of the first anchor to the be prev of the last anchor firstAnchor.setPrevPos(lastAnchor.getPrevX(), lastAnchor.getPrevY(), lastAnchor.getPrevZ()); //only if we have more than two anchor points, remove the last point if (lastAnchorIndex > 1) { //remove the last anchor from the subpath this._selectedSubpath.removeAnchor(lastAnchorIndex); } else { //set the next of the last anchor to the be next of the first anchor lastAnchor.setPrevPos(firstAnchor.getNextX(), firstAnchor.getNextY(), firstAnchor.getNextZ()); } } } } this._snapTarget = null; var drawData = this.getDrawingData(); if (drawData) { if (!this._penPlaneMat) { this._penPlaneMat = drawData.planeMat; } if (this._isNewPath) { var strokeSize = 1.0;//default stroke width if (this.options.strokeSize) { strokeSize = ShapesController.GetValueInPixels(this.options.strokeSize.value, this.options.strokeSize.units); } this._selectedSubpath.setStrokeWidth(strokeSize); if (this.application.ninja.colorController.colorToolbar.stroke.webGlColor){ this._selectedSubpath.setStrokeColor(this.application.ninja.colorController.colorToolbar.stroke.webGlColor); } if (this.application.ninja.colorController.colorToolbar.fill.webGlColor){ this._selectedSubpath.setFillColor(this.application.ninja.colorController.colorToolbar.fill.webGlColor); } } //if this is a new path being rendered this._selectedSubpath.makeDirty(); this._selectedSubpath.createSamples(); //if we have some samples to render... if (this._selectedSubpath.getNumAnchors() > 1) { this.ShowSelectedSubpath(); } //if (this._selectedSubpath.getNumPoints() > 0) { } //if drawData //always assume that we're not starting a new path anymore this._isNewPath = false; this.endDraw(event); this._isDrawing = false; this._hasDraw = false; this._editMode = this.EDIT_NONE; this.DrawHandles(); //if (this._entryEditMode === this.ENTRY_SELECT_PATH || !this._selectedSubpath.getIsClosed()){ if (this._selectedSubpath){ this.DrawSubpathAnchors(this._selectedSubpath);//render the subpath anchors on canvas (not GL) } //} if (!g_DoPenToolMouseMove){ NJevent("disableStageMove"); } this._hoveredAnchorIndex = -1; } }, //override the Alt key handlers from drawing-tool.js HandleAltKeyDown: { value: function (event) { this._isAltDown = true; } }, HandleAltKeyUp: { value: function (event) { this._isAltDown = false; } }, HandleEscape: { value: function(event) { this._isEscapeDown = true; //close the current subpath and reset the pen tool this._penCanvas = null; this._penPlaneMat = null; this._snapTarget = null; this._selectedSubpath = null; this.application.ninja.stage.clearDrawingCanvas(); } }, HandleKeyUp: { value: function(event){ if (!(event.target instanceof HTMLInputElement)) { if(event.altKey || event.keyCode === 18) { //for key up, the event.altKey is false when it should be true, so I also check keyCode this.HandleAltKeyUp(event); } else { console.log("Pen tool Unhandled key up:", event.keyCode); } } } }, HandleKeyPress: { value: function(event){ var inc, currentValue, moveCommand; if (!(event.target instanceof HTMLInputElement)) { if(event.shiftKey) { this.HandleShiftKeyDown(event); } else if(event.altKey) { this.HandleAltKeyDown(event); } else if (event.keyCode === Keyboard.SPACE) { event.preventDefault(); this.HandleSpaceKeyDown(event); } else if (event.keyCode == Keyboard.BACKSPACE || event.keyCode === Keyboard.DELETE) { //this is probably unnecessary since we handle delete and backspace via the delete delegate event.stopImmediatePropagation(); event.preventDefault(); } else if (event.keyCode === Keyboard.ESCAPE){ this.HandleEscape(event); } else { console.log("Pen tool Unhandled key press:", event.keyCode); } } } }, handleScroll: { value: function(event) { this.application.ninja.stage.clearDrawingCanvas();//stageManagerModule.stageManager.clearDrawingCanvas(); this.DrawSubpathAnchors(this._selectedSubpath); } }, RenderShape: { value: function (w, h, planeMat, midPt, canvas) { if ((Math.floor(w) === 0) || (Math.floor(h) === 0)) { return; } var left = Math.round(midPt[0] - 0.5 * w); var top = Math.round(midPt[1] - 0.5 * h); if (!canvas) { var newCanvas = null; newCanvas = NJUtils.makeNJElement("canvas", "Subpath", "shape", {"data-RDGE-id": NJUtils.generateRandom()}, true); var elementModel = TagTool.makeElement(parseInt(w), parseInt(h), planeMat, midPt, newCanvas, this._useWebGL); ElementMediator.addElements(newCanvas, elementModel.data, false); // create all the GL stuff var world = this.getGLWorld(newCanvas, this._useWebGL);//this.options.use3D);//this.CreateGLWorld(planeMat, midPt, newCanvas, this._useWebGL);//fillMaterial, strokeMaterial); //store a reference to this newly created canvas this._penCanvas = newCanvas; var subpath = this._selectedSubpath; //new GLSubpath(); subpath.setWorld(world); subpath.setCanvas(newCanvas); subpath.setPlaneMatrix(planeMat); var planeMatInv = glmat4.inverse( planeMat, [] ); subpath.setPlaneMatrixInverse(planeMatInv); subpath.setPlaneCenter(midPt); world.addObject(subpath); world.render(); //TODO this will not work if there are multiple shapes in the same canvas newCanvas.elementModel.shapeModel.GLGeomObj = subpath; newCanvas.elementModel.shapeModel.shapeCount++; if(newCanvas.elementModel.shapeModel.shapeCount === 1) { newCanvas.elementModel.selection = "Subpath"; newCanvas.elementModel.pi = "SubpathPi"; newCanvas.elementModel.shapeModel.strokeSize = this.options.strokeSize.value + " " + this.options.strokeSize.units; newCanvas.elementModel.shapeModel.GLGeomObj = subpath; newCanvas.elementModel.shapeModel.useWebGl = this.options.use3D; } else { // TODO - update the shape's info only. shapeModel will likely need an array of shapes. } if(newCanvas.elementModel.isShape) { 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);//this.options.use3D);//this.CreateGLWorld(planeMat, midPt, canvas, this._useWebGL);//fillMaterial, strokeMaterial); } 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", "penTool");//DocumentControllerModule.DocumentController.SetElementStyle(canvas, "left", parseInt(left) + "px"); ElementMediator.setProperty(canvasArray, "top", [parseInt(top) + "px"],"Changing", "penTool");//DocumentControllerModule.DocumentController.SetElementStyle(canvas, "top", parseInt(top) + "px"); ElementMediator.setProperty(canvasArray, "width", [w+"px"], "Changing", "penTool");//canvas.width = w; ElementMediator.setProperty(canvasArray, "height", [h+"px"], "Changing", "penTool");//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 subpath = this._selectedSubpath; subpath.setDrawingTool(this); subpath.setPlaneMatrix(planeMat); var planeMatInv = glmat4.inverse( planeMat, [] ); subpath.setPlaneMatrixInverse(planeMatInv); subpath.setPlaneCenter(midPt); subpath.setWorld(world); world.addIfNewObject(subpath); world.render(); //TODO this will not work if there are multiple shapes in the same canvas canvas.elementModel.shapeModel.GLGeomObj = subpath; } //else of if (!canvas) { } //value: function (w, h, planeMat, midPt, canvas) { }, //RenderShape: { BuildSecondCtrlPoint:{ value: function(p0, p2, p3) { var baselineOrig = VecUtils.vecSubtract(3, p3, p0); var baseline = VecUtils.vecNormalize(3, baselineOrig); var delta = VecUtils.vecSubtract(3, p2, p3); //component of the delta along baseline var deltaB = baseline; VecUtils.vecScale(3, deltaB, VecUtils.vecDot(3, baseline, delta)); //component of the delta orthogonal to baseline var deltaO = VecUtils.vecSubtract(3, delta, deltaB); var p1 = VecUtils.vecInterpolate(3, p0, p3, 0.3333); p1= VecUtils.vecAdd(3, p1,deltaO); return p1; } //value: function(p0, p2, p3) { }, //BuildSecondCtrlPoint:{ HandleDoubleClick: { value: function () { //if there is a selected anchor point if (this._selectedSubpath && this._selectedSubpath.getSelectedAnchorIndex() !== -1) { var selAnchor = this._selectedSubpath.getAnchor(this._selectedSubpath.getSelectedAnchorIndex()); var pos = [selAnchor.getPosX(), selAnchor.getPosY(), selAnchor.getPosZ()]; var distToPrev = selAnchor.getPrevDistanceSq(pos[0], pos[1], pos[2]); var distToNext = selAnchor.getNextDistanceSq(pos[0], pos[1], pos[2]); var threshSq = 0; // 4 * this._PICK_POINT_RADIUS * this._PICK_POINT_RADIUS; //if either prev or next are within threshold distance to anchor position if (distToPrev <= threshSq || distToNext <= threshSq) { var haveNext = false; var havePrev = false; var numAnchors = this._selectedSubpath.getNumAnchors(); if (numAnchors>1 && (this._selectedSubpath.getSelectedAnchorIndex() < (numAnchors-1) || this._selectedSubpath.getIsClosed())){ //there is an anchor point after this one var nextAnchor = null; if (this._selectedSubpath.getSelectedAnchorIndex() < (numAnchors-1)) nextAnchor = this._selectedSubpath.getAnchor(this._selectedSubpath.getSelectedAnchorIndex()+1); else nextAnchor = this._selectedSubpath.getAnchor(0); var nextAnchorPrev = [nextAnchor.getPrevX(), nextAnchor.getPrevY(), nextAnchor.getPrevZ()]; var nextAnchorPos = [nextAnchor.getPosX(), nextAnchor.getPosY(), nextAnchor.getPosZ()]; var newNext = this.BuildSecondCtrlPoint(pos, nextAnchorPrev, nextAnchorPos); selAnchor.setNextPos(newNext[0], newNext[1], newNext[2]); //check if the next is still not over the threshSq..if so, add a constant horizontal amount if (selAnchor.getNextDistanceSq(pos[0], pos[1], pos[2]) <= threshSq) { selAnchor.setNextPos(newNext[0]+ (3 * this._PICK_POINT_RADIUS), newNext[1], newNext[2]); } haveNext = true; } if (numAnchors>1 && (this._selectedSubpath.getSelectedAnchorIndex() > 0 || this._selectedSubpath.getIsClosed())){ //there is an anchor point before this one var prevAnchor = null; if (this._selectedSubpath.getSelectedAnchorIndex() > 0) prevAnchor = this._selectedSubpath.getAnchor(this._selectedSubpath.getSelectedAnchorIndex()-1); else prevAnchor = this._selectedSubpath.getAnchor(numAnchors-1); var prevAnchorNext = [prevAnchor.getNextX(), prevAnchor.getNextY(), prevAnchor.getNextZ()]; var prevAnchorPos = [prevAnchor.getPosX(), prevAnchor.getPosY(), prevAnchor.getPosZ()]; var newPrev = this.BuildSecondCtrlPoint(pos, prevAnchorNext, prevAnchorPos); selAnchor.setPrevPos(newPrev[0], newPrev[1], newPrev[2]); //check if the prev is still not over the threshSq..if so, add a constant horizontal amount if (selAnchor.getPrevDistanceSq(pos[0], pos[1], pos[2]) <= threshSq) { selAnchor.setPrevPos(newPrev[0]+ (3 * this._PICK_POINT_RADIUS), newPrev[1], newPrev[2]); } havePrev = true; } if (haveNext && !havePrev){ selAnchor.setPrevFromNext(); } else if (havePrev && !haveNext){ selAnchor.setNextFromPrev(); } else if (!haveNext && !havePrev){ selAnchor.setNextPos(pos[0]+ (3 * this._PICK_POINT_RADIUS), pos[1], pos[2]); selAnchor.setPrevFromNext(); } } // if (distToPrev < threshSq) { else { //bring points close (to exactly same position) selAnchor.setNextPos(pos[0], pos[1], pos[2]); selAnchor.setPrevPos(pos[0], pos[1], pos[2]); } // else of if (distToPrev < threshSq) { this._selectedSubpath.makeDirty(); this._selectedSubpath.createSamples(); this.ShowSelectedSubpath(); //clear the canvas before we draw anything else this.application.ninja.stage.clearDrawingCanvas();//stageManagerModule.stageManager.clearDrawingCanvas(); this.DrawSubpathAnchors(this._selectedSubpath); } //if (this._selectedSubpath && this._selectedSubpath.getSelectedAnchorIndex() !== -1) } //value: function () { }, //HandleDoubleClick: { _getScreenCoord: { value: function (hitRec) { var sRet = hitRec.getScreenPoint(); sRet[2] = 0; return sRet; } }, //_getScreenCoord: // DrawSubpathSVG // Draw the subpath using the SVG drawing capability (i.e. not WebGL) DrawSubpathSVG: { value: function (subpath) { if (subpath === null) return; subpath.createSamples(); //dirty bit will be checked inside this function var numAnchors = subpath.getNumAnchors(); if (numAnchors === 0) return; var ctx = this.application.ninja.stage.drawingContext;//stageManagerModule.stageManager.drawingContext; if (ctx === null) throw ("null drawing context in Pentool::DrawSubpathSVG"); ctx.save(); var horizontalOffset = this.application.ninja.stage.userContentLeft; var verticalOffset = this.application.ninja.stage.userContentTop; //display the subpath as a sequence of cubic beziers ctx.lineWidth = 1;//TODO replace hardcoded stroke width with some programmatically set value (should not be same as stroke width) ctx.strokeStyle = "green"; //if (subpath.getStrokeColor()) // ctx.strokeStyle = MathUtils.colorToHex( subpath.getStrokeColor() ); ctx.beginPath(); var p0x = subpath.getAnchor(0).getPosX()+ horizontalOffset; var p0y = subpath.getAnchor(0).getPosY()+ verticalOffset; ctx.moveTo(p0x, p0y); for (var i = 1; i < numAnchors; i++) { var p1x = subpath.getAnchor(i - 1).getNextX()+ horizontalOffset; var p1y = subpath.getAnchor(i - 1).getNextY()+ verticalOffset; var p2x = subpath.getAnchor(i).getPrevX()+ horizontalOffset; var p2y = subpath.getAnchor(i).getPrevY()+ verticalOffset; var p3x = subpath.getAnchor(i).getPosX()+ horizontalOffset; var p3y = subpath.getAnchor(i).getPosY()+ verticalOffset; ctx.bezierCurveTo(p1x, p1y, p2x, p2y, p3x, p3y); } if (subpath.getIsClosed()) { var i = numAnchors - 1; var p1x = subpath.getAnchor(i).getNextX()+ horizontalOffset; var p1y = subpath.getAnchor(i).getNextY()+ verticalOffset; var p2x = subpath.getAnchor(0).getPrevX()+ horizontalOffset; var p2y = subpath.getAnchor(0).getPrevY()+ verticalOffset; var p3x = subpath.getAnchor(0).getPosX()+ horizontalOffset; var p3y = subpath.getAnchor(0).getPosY()+ verticalOffset; ctx.bezierCurveTo(p1x, p1y, p2x, p2y, p3x, p3y); } ctx.stroke(); ctx.restore(); } //function (subpath) }, //DrawSubpathSVG DrawSubpathAnchors: { value: function (subpath) { if (subpath === null) return; var numAnchors = subpath.getNumAnchors(); if (numAnchors === 0) return; var ctx = this.application.ninja.stage.drawingContext;//stageManagerModule.stageManager.drawingContext; if (ctx === null) throw ("null drawing context in Pentool::DrawSelectedSubpathAnchors"); ctx.save(); var horizontalOffset = this.application.ninja.stage.userContentLeft;//stageManagerModule.stageManager.userContentLeft; var verticalOffset = this.application.ninja.stage.userContentTop;//stageManagerModule.stageManager.userContentTop; //display circles and squares near all control points ctx.fillStyle = "#FFFFFF"; ctx.lineWidth = 1; ctx.strokeStyle = "green"; var anchorDelta = 2; var selAnchorDelta = 4; for (var i = 0; i < numAnchors; i++) { var px = subpath.getAnchor(i).getPosX(); var py = subpath.getAnchor(i).getPosY(); ctx.beginPath(); //ctx.arc(px + horizontalOffset, py + verticalOffset, this._DISPLAY_ANCHOR_RADIUS, 0, 2 * Math.PI, false); ctx.moveTo(px-anchorDelta+horizontalOffset, py-anchorDelta+verticalOffset); ctx.lineTo(px+anchorDelta+horizontalOffset, py-anchorDelta+verticalOffset); ctx.lineTo(px+anchorDelta+horizontalOffset, py+anchorDelta+verticalOffset); ctx.lineTo(px-anchorDelta+horizontalOffset, py+anchorDelta+verticalOffset); ctx.closePath(); ctx.fill(); ctx.stroke(); } //display the hovered over anchor point ctx.lineWidth = 2; if (this._hoveredAnchorIndex>=0 && this._hoveredAnchorIndex=0){ this._hoveredAnchorIndex=-1; this._selectedSubpath.removeAnchor(this._selectedSubpath.getSelectedAnchorIndex()); this._selectedSubpath.createSamples(); //clear the canvas this.application.ninja.stage.clearDrawingCanvas();//stageManagerModule.stageManager.clearDrawingCanvas(); this.DrawSubpathAnchors(this._selectedSubpath); this.ShowSelectedSubpath(); } else { this._selectedSubpath.clearAllAnchors(); //perhaps unnecessary this._selectedSubpath = null; //clear the canvas this.application.ninja.stage.clearDrawingCanvas();//stageManagerModule.stageManager.clearDrawingCanvas(); //undo/redo...go through ElementController and NJEvent var els = []; ElementController.removeElement(this._penCanvas); els.push(this._penCanvas); NJevent( "elementsRemoved", els ); this._penCanvas = null; } } //do nothing if there was no selected subpath and if there was no selection } else { //undo/redo...go through ElementMediator (see ElementMediator.handleDeleting() from where the much of this function is copied) //clear the canvas this.application.ninja.stage.clearDrawingCanvas();//stageManagerModule.stageManager.clearDrawingCanvas(); var els = []; for(var i = 0; i