/* <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 Montage = require("montage/core/core").Montage; var Component = require("montage/ui/component").Component; var snapManager = require("js/helper-classes/3D/snap-manager").SnapManager; var viewUtils = require("js/helper-classes/3D/view-utils").ViewUtils; var vecUtils = require("js/helper-classes/3D/vec-utils").VecUtils; var drawUtils = require("js/helper-classes/3D/draw-utils").DrawUtils; exports.DrawingToolBase = Montage.create(Component, { dragPlane: { value: null }, /** * Used on the initial MouseDown for Drawing Tools * * Returns: An array containing: * 0 - HitRec point * 1 - X value converted to screen point * 2 - Y value converted to screen point */ getInitialSnapPoint: { value: function(x, y, shapeCanvas) { // update the snap settings snapManager.enableSnapAlign( snapManager.snapAlignEnabledAppLevel() ); snapManager.enableElementSnap( snapManager.elementSnapEnabledAppLevel() ); snapManager.enableGridSnap( snapManager.gridSnapEnabledAppLevel() ); // do the snap this.dragPlane = null; var hitRec = snapManager.snap(x, y, true); if (hitRec) { if (shapeCanvas) { this.dragPlane = viewUtils.getUnprojectedElementPlane( shapeCanvas ); snapManager.setupDragPlaneFromPlane( this.dragPlane ); } else { this.dragPlane = snapManager.setupDragPlanes( hitRec, true ); } // console.log( "drag plane: " + this.dragPlane ); var wpHitRec = hitRec.convertToWorkingPlane( this.dragPlane ); var pt = hitRec.getScreenPoint(); return( [wpHitRec, pt[0], pt[1]] ); } } }, /** * Used on the Mouse Move to calculate new snap point. */ getUpdatedSnapPoint: { value: function(x,y, snap3d, downHitRec) { // update the snap settings snapManager.enableSnapAlign( snapManager.snapAlignEnabledAppLevel() ); snapManager.enableElementSnap( snapManager.elementSnapEnabledAppLevel() ); snapManager.enableGridSnap( snapManager.gridSnapEnabledAppLevel() ); var hitRec = snapManager.snap(x, y, snap3d ); if (hitRec) { // if ((hitRec.getType() !== hitRec.SNAP_TYPE_STAGE) && !hitRec.isSomeGridTypeSnap()) { // hitRec = hitRec.convertToWorkingPlane( snapManager.getDragPlane() ); // } // // if(downHitRec !== null) { // // if we are working off-plane, do a snap to the projected locations of the geometry // var thePlane = workingPlane; // if (snapManager.hasDragPlane()) // { // thePlane = snapManager.getDragPlane(); // } // // // Return the up HitRec // return hitRec; // } else { // return null; // } if(downHitRec) { hitRec = hitRec.convertToWorkingPlane(this.dragPlane); } else if ((hitRec.getType() !== hitRec.SNAP_TYPE_STAGE) && !hitRec.isSomeGridTypeSnap()) { hitRec = hitRec.convertToWorkingPlane( snapManager.getDragPlane() ); } } return hitRec; } }, getUpdatedSnapPointNoAppLevelEnabling: { value: function(x,y, snap3d, downHitRec) { // do the first snap var hitRec = snapManager.snap(x, y, snap3d ); if (hitRec) { if ((hitRec.getType() !== hitRec.SNAP_TYPE_STAGE) && !hitRec.isSomeGridTypeSnap()) { hitRec = hitRec.convertToWorkingPlane( snapManager.getDragPlane() ); } if(downHitRec !== null) { // if we are working off-plane, do a snap to the projected locations of the geometry var thePlane = workingPlane; if (snapManager.hasDragPlane()) { thePlane = snapManager.getDragPlane(); } // Return the up HitRec return hitRec; } else { return null; } } } }, setDownHitRec: { value: function (x, y, do3DSnap) { var hitRec = snapManager.snap(x, y, do3DSnap ); if (hitRec) { if ((hitRec.getType() != hitRec.SNAP_TYPE_STAGE) && !hitRec.isSomeGridTypeSnap()) { //hitRec = hitRec.convertToWorkingPlane( workingPlane ); snapManager.setupDragPlanes(hitRec); hitRec = hitRec.convertToWorkingPlane( snapManager.getDragPlane() ); } return hitRec; } } }, drawSnapLastHit: { value: function() { snapManager.drawLastHit(); } }, getHitRecPos: { value: function (hitRec) { if (!hitRec) return null; // get the hit rec. points in plane space var psPos = hitRec.getLocalPoint(); var stageOffset = viewUtils.getElementOffset(this.application.ninja.currentDocument.model.documentRoot); viewUtils.setViewportObj(this.application.ninja.currentDocument.model.documentRoot); // get the matrix taking the local hit point in plane space // to world space of whatever element it is in. var planeMat = hitRec.getPlaneMatrix(); // get the center of the circle in stage world space var swPos = viewUtils.postViewToStageWorld(MathUtils.transformPoint(psPos, hitRec.getPlaneMatrix()), hitRec.getElt()); //var swPos = hitRec.calculateStageWorldPoint(); todo figure out why we cannot just use this function instead of the above // the stage world space point is now relative to the center of the 3D space. To // calculate the left and top offsets, this must be offset by the stage dimensions swPos[0] += snapManager.getStageWidth() / 2.0; swPos[1] += snapManager.getStageHeight() / 2.0; return swPos; } }, getCompletePoints: { value: function(hitRec0, hitRec1) { if (hitRec0 && hitRec1) { // get the 2 snap points in plane space var p0 = hitRec0.getLocalPoint(), p1 = hitRec1.getLocalPoint(); var stageOffset = viewUtils.getElementOffset(this.application.ninja.currentDocument.model.documentRoot); viewUtils.setViewportObj(this.application.ninja.currentDocument.model.documentRoot); // get the matrix taking the local hit point in plane space // to world space of whatever element it is in. var planeMat = hitRec0.getPlaneMatrix(); // get the center of the circle in stage world space var s0 = viewUtils.postViewToStageWorld( MathUtils.transformPoint(p0,hitRec0.getPlaneMatrix()), hitRec0.getElt() ), s1 = viewUtils.postViewToStageWorld( MathUtils.transformPoint(p1,hitRec1.getPlaneMatrix()), hitRec1.getElt() ); // apply the projected snap points var s0Proj = false, s1Proj = false; // find a "reasonable" plane var thePlane = workingPlane.slice(0); if (snapManager.hasDragPlane()) { thePlane = snapManager.getDragPlane(); } var d0 = vecUtils.vecDot( 3, thePlane, s0 ) + thePlane[3], d1 = vecUtils.vecDot( 3, thePlane, s1 ) + thePlane[3]; var sign0 = MathUtils.fpSign( d0 ), sign1 = MathUtils.fpSign( d1 ); if ((sign0 !== 0) || (sign1 !== 0)) { // we need to pick a different plane if ( MathUtils.fpCmp(d0,d1) === 0 ) { thePlane[3] = -vecUtils.vecDot(3, thePlane, s0); } else { var vec = vecUtils.vecSubtract(3, s1, s0 ); var yAxis = [0,1,0]; var tmp = vecUtils.vecCross( 3, vec, yAxis ); var mag = vecUtils.vecMag(3, tmp); if (MathUtils.fpSign(mag) === 0) { thePlane = [0,0,1]; thePlane[3] = -vecUtils.vecDot(3, thePlane, s0); } else { var xAxis = vecUtils.vecCross( 3, yAxis, tmp ); thePlane = vecUtils.vecCross( 3, xAxis, yAxis ); vecUtils.vecNormalize(3, thePlane, 1.0 ); thePlane[3] = -vecUtils.vecDot(3, thePlane, s0); } } // recompute the plane matrix planeMat = drawUtils.getPlaneToWorldMatrix(thePlane, MathUtils.getPointOnPlane(thePlane)); } // unproject the bounds //var planeMatInv = planeMat.inverse(); var planeMatInv = glmat4.inverse( planeMat, [] ); //var midPt = this.unprojectPoints( s0, s1, planeMat, planeMatInv, s1Proj ); // var midPt = this.unprojectPoints( s0, s1, planeMat, planeMatInv, true ); // get the 4 points of the bounding box in 2D space p0 = MathUtils.transformPoint( s0, planeMatInv ); p1 = MathUtils.transformPoint( s1, planeMatInv ); // determine the midpoint of the oval //midPt = s0.add(s1); //midPt = midPt.multiply(0.5); //midPt = MathUtils.makeDimension3(midPt); var midPt = vec3.add(s0, s1, []); midPt = vecUtils.vecScale( 3, midPt, 0.5 ); // the mid point is now relative to the center of the 3D space. To // calculate the left and top offsets, this must be offset by the stage dimensions midPt[0] += snapManager.getStageWidth() / 2.0; midPt[1] += snapManager.getStageHeight() / 2.0; // calculate the width and height. var left = p0[0]; var top = p0[1]; var right = p1[0]; var bottom = p1[1]; var w = Math.abs(right - left), h = Math.abs(bottom - top); // return ({"width":w, "height":h, "planeMat":planeMat, "midPt":midPt}); var s0Offset = s0.slice(0); var s1Offset = s1.slice(0); s0Offset[0] += snapManager.getStageWidth() / 2.0; s0Offset[1] += snapManager.getStageHeight() / 2.0; s1Offset[0] += snapManager.getStageWidth() / 2.0; s1Offset[1] += snapManager.getStageHeight() / 2.0; return ({ "width": w, "height": h, "planeMat": planeMat, "midPt": midPt, "mouseDownPos": s0Offset, "mouseUpPos": s1Offset }); } else { return null } } }, cleanupSnap: { value: function() { // set the drag plane to the working plane snapManager.clear2DCache(); snapManager.setupDragPlaneFromPlane( workingPlane ); // update the snap settings snapManager.enableSnapAlign( snapManager.snapAlignEnabledAppLevel() ); snapManager.enableElementSnap( snapManager.elementSnapEnabledAppLevel() ); snapManager.enableGridSnap( snapManager.gridSnapEnabledAppLevel() ); } }, draw2DRectangle: { value: function(x0, y0, x1, y1) { var drawingContext = this.application.ninja.stage.drawingContext, drawingPrefs = this.application.ninja.stage.drawingContextPreferences; this.application.ninja.stage.clearDrawingCanvas(); //TODO Save and restore state drawingContext.strokeStyle = drawingPrefs.color; drawingContext.lineWidth = drawingPrefs.thickness; drawingContext.strokeRect(x0 - 0.5 ,y0 - 0.5 ,x1 ,y1); } }, /** * Feedback drawing functions */ drawRectangle: { value: function(hitRec0, hitRec1) { var p0 = hitRec0.getLocalPoint(), p1 = hitRec1.getLocalPoint(); var stageMat = viewUtils.getMatrixFromElement(this.application.ninja.currentDocument.model.documentRoot); var elt = hitRec0.getElt(); if (!elt) { elt = hitRec1.getElt(); } if (!elt) { elt = this.application.ninja.currentDocument.model.documentRoot; } if (elt) { viewUtils.pushViewportObj(elt); var offset = viewUtils.getElementOffset(elt); offset[2] = 0; // check the plane to draw the rectangle on var s0 = viewUtils.postViewToStageWorld( MathUtils.transformPoint(p0,hitRec0.getPlaneMatrix()), hitRec0.getElt() ), s1 = viewUtils.postViewToStageWorld( MathUtils.transformPoint(p1,hitRec1.getPlaneMatrix()), hitRec1.getElt() ); // find a "reasonable" plane var planeMat = hitRec0.getPlaneMatrix(); var planeMatInv; var thePlane = workingPlane.slice(0); if (snapManager.hasDragPlane()) { thePlane = snapManager.getDragPlane(); } var d0 = vecUtils.vecDot( 3, thePlane, s0 ) + thePlane[3], d1 = vecUtils.vecDot( 3, thePlane, s1 ) + thePlane[3]; var sign0 = MathUtils.fpSign( d0 ), sign1 = MathUtils.fpSign( d1 ); if ((sign0 !== 0) || (sign1 !== 0)) { // we need to pick a different plane if ( MathUtils.fpCmp(d0,d1) === 0 ){ thePlane[3] = -vecUtils.vecDot(3, thePlane, s0); } else { var vec = vecUtils.vecSubtract(3, s1, s0 ); var yAxis = [0,1,0]; var tmp = vecUtils.vecCross( 3, vec, yAxis ); var mag = vecUtils.vecMag(3, tmp); if (MathUtils.fpSign(mag) === 0) { thePlane = [0,0,1]; thePlane[3] = -vecUtils.vecDot(3, thePlane, s0); } else { var xAxis = vecUtils.vecCross( 3, yAxis, tmp ); thePlane = vecUtils.vecCross( 3, xAxis, yAxis ); vecUtils.vecNormalize(3, thePlane, 1.0 ); thePlane[3] = -vecUtils.vecDot(3, thePlane, s0); } } // recompute the plane matrix planeMat = drawUtils.getPlaneToWorldMatrix(thePlane, MathUtils.getPointOnPlane(thePlane)); //planeMatInv = planeMat.inverse(); planeMatInv = glmat4.inverse( planeMat, [] ); p0 = MathUtils.transformPoint(p0,hitRec0.getPlaneMatrix()); p0 = MathUtils.transformPoint(p0, planeMatInv); p1 = MathUtils.transformPoint(p1,hitRec1.getPlaneMatrix()); p1 = MathUtils.transformPoint(p1, planeMatInv); } else { //planeMatInv = planeMat.inverse(); planeMatInv = glmat4.inverse( planeMat, [] ); } // determine if the geometry is going to be projected. If so a second projected rectangle is drawn var isProjected = ((MathUtils.fpCmp(thePlane[2],1.0) !== 0) || (MathUtils.fpSign(thePlane[3]) !== 0)); // TODO - We no longer need to project drawing after perspective fix. Need to clean up this code. // For now, just setting isProjected to false so rest of the drawing still works. isProjected = false; // get and draw the unprojected object points var projPtArr = []; if (isProjected) { this.getProjectedObjectPoints( s0, s1, planeMat, planeMatInv, elt, stageMat, offset, projPtArr ); } var localPt = [p0[0], p0[1], 0.0, 1.0]; s0 = viewUtils.postViewToStageWorld( MathUtils.transformPoint(localPt,planeMat), elt ); s0 = vecUtils.vecAdd(3, viewUtils.viewToScreen( MathUtils.transformPoint(s0, stageMat) ), offset ); localPt[1] = p1[1]; s1 = viewUtils.postViewToStageWorld( MathUtils.transformPoint(localPt,planeMat), elt ); s1 = vecUtils.vecAdd(3, viewUtils.viewToScreen( MathUtils.transformPoint(s1, stageMat) ), offset ); localPt[0] = p1[0]; var s2 = viewUtils.postViewToStageWorld( MathUtils.transformPoint(localPt,planeMat), elt ); s2 = vecUtils.vecAdd(3, viewUtils.viewToScreen( MathUtils.transformPoint(s2, stageMat) ), offset ); localPt[1] = p0[1]; var s3 = viewUtils.postViewToStageWorld( MathUtils.transformPoint(localPt,planeMat), elt ); s3 = vecUtils.vecAdd(3, viewUtils.viewToScreen( MathUtils.transformPoint(s3, stageMat) ), offset ); if ( isProjected) { var unprojPtArr = [s0, s1, s2, s3]; this.application.ninja.stage.draw3DProjectedAndUnprojectedRectangles( unprojPtArr, projPtArr ); } else { this.application.ninja.stage.draw3DSelectionRectangle(s0[0], s0[1], s1[0], s1[1], s2[0], s2[1], s3[0], s3[1], s0[0], s0[1]); } viewUtils.popViewportObj(); } } }, drawLine: { value: function (hitRec0, hitRec1, strokeSize, strokeColor) { var p0 = hitRec0.getScreenPoint(), p1 = hitRec1.getScreenPoint(); this.application.ninja.stage.drawLine(p0[0], p0[1], p1[0], p1[1], strokeSize, strokeColor); } }, /** * Draw Helper Functions */ /** * Returns a perfect square using the top/left/bottom/right values. */ toSquare: { value: function(x0,x1,y0,y1) { var dw = 1; var dh = 1; var w = x1 - x0, h = y1 - y0; if(w < 0) dw = -1; if(h < 0) dh = -1; if(Math.abs(w) >= Math.abs(h)) { h = (Math.abs(w) * dh); } else { w = (Math.abs(h) * dw); } return [x0,y0,w,h]; } }, toCenterRectangle: { value: function(x0,x1,y0,y1) { var x,y,w,h = 0; x = x0 - (x1 - x0); y = y0 - (y1 - y0); w = x1 - x; h = y1 - y; return [x,y,w,h]; } }, /** * Helper Functions */ unprojectPoints: { value: function( s0In, s1In, planeMat, planeMatInv, fixedS1 ) { var s0 = s0In.slice(0); var s2 = s1In.slice(0); // calculate the mid point of the rectangle var midPt = vecUtils.vecAdd(3, s0, s2); vecUtils.vecScale(3, midPt, 0.5); s0[0] -= midPt[0]; s0[1] -= midPt[1]; s2[0] -= midPt[0]; s2[1] -= midPt[1]; // convert the 2 world space points to plane space var p0 = MathUtils.transformPoint( s0, planeMatInv ), p2 = MathUtils.transformPoint( s2, planeMatInv ); var z = p0[2]; // fill in the other 2 points on the plane to complete the 4 points var p1 = [p0[0], p2[1], z], p3 = [p2[0], p0[1], z]; // convert back to 3D space s0 = MathUtils.transformPoint( p0, planeMat ); var s1 = MathUtils.transformPoint( p1, planeMat ); s2 = MathUtils.transformPoint( p2, planeMat ); var s3 = MathUtils.transformPoint( p3, planeMat ); // unproject the 4 points var i; var p = 1400; var ptArr = [ s0, s1, s2, s3 ]; for (i=0; i<4; i++) { pt = ptArr[i]; if (MathUtils.fpCmp(p,-pt[2]) !== 0){ z = pt[2]*p/(p + pt[2]); var x = pt[0]*(p - z)/p, y = pt[1]*(p - z)/p; pt[0] = x; pt[1] = y; pt[2] = z; } } // back to 2D space... for (i=0; i<4; i++) ptArr[i] = MathUtils.transformPoint( ptArr[i], planeMatInv ); // find the 2 diagonal points to use if (fixedS1) { p0 = ptArr[0]; p1 = ptArr[1]; p2 = ptArr[2]; p3 = ptArr[3]; pt0 = p0.slice(0); pt1 = p2.slice(0); z = pt0[2]; } else { p0 = ptArr[0]; p1 = ptArr[1]; p2 = ptArr[2]; p3 = ptArr[3]; z = p0[2]; var pt0 = p0.slice(0), pt1 = p2.slice(0); if (p0[0] < p2[0]){ pt0[0] = Math.max(p0[0],p1[0]); pt1[0] = Math.min(p2[0],p3[0]); } else { pt0[0] = Math.min(p0[0],p1[0]); pt1[0] = Math.max(p2[0],p3[0]); } if (p0[1] < p2[1]){ pt0[1] = Math.max(p0[1],p3[1]); pt1[1] = Math.min(p1[1],p2[1]); } else { pt0[1] = Math.min(p0[1],p3[1]); pt1[1] = Math.max(p1[1],p2[1]); } } pt0[2] = z; pt1[2] = z; var ctr = vecUtils.vecAdd(3, pt0, pt1); vecUtils.vecScale( 3, ctr, 0.5 ); // put the diagonal points back in 3D space s0 = MathUtils.transformPoint( pt0, planeMat ); s1 = MathUtils.transformPoint( pt1, planeMat ); ctr = MathUtils.transformPoint( ctr, planeMat ); // add the translation back in s0[0] += midPt[0]; s0[1] += midPt[1]; s1[0] += midPt[0]; s1[1] += midPt[1]; ctr[0] += midPt[0]; ctr[1] += midPt[1]; // set the returned values s0In[0] = s0[0]; s0In[1] = s0[1]; s0In[2] = s0[2]; s1In[0] = s1[0]; s1In[1] = s1[1]; s1In[2] = s1[2]; return ctr; } }, getProjectedObjectPoints: { value: function( u0, u1, planeMat, planeMatInv, elt, stageMat, offset, rtnScrPts ){ var s0 = u0.slice(0); var s2 = u1.slice(0); var i, z; // calculate the mid point of the rectangle var midPt = vecUtils.vecAdd(3, s0, s2); vecUtils.vecScale(3, midPt, 0.5); s0[0] -= midPt[0]; s0[1] -= midPt[1]; s2[0] -= midPt[0]; s2[1] -= midPt[1]; // unproject the 2 points var p = 1400; var ptArr = [ s0, s2 ]; for (i=0; i<2; i++) { pt = ptArr[i]; if (MathUtils.fpCmp(p,-pt[2]) !== 0) { z = pt[2]*p/(p + pt[2]); var x = pt[0]*(p - z)/p, y = pt[1]*(p - z)/p; y = pt[1]*(p - z)/p; pt[0] = x; pt[1] = y; pt[2] = z; } } // back to 2D space... for (i=0; i<2; i++) ptArr[i] = MathUtils.transformPoint( ptArr[i], planeMatInv ); // fill in the other 2 points on the plane to complete the 4 points var pt0 = ptArr[0], pt2 = ptArr[1]; z = pt0[2]; pt0[2] = z; pt2[2] = z; var pt1 = [pt0[0], pt2[1], z], pt3 = [pt2[0], pt0[1], z]; // ptArr = [pt0, pt1, pt2, pt3]; var pt; var dist, scale, pDist = 1400; // elt.webkitTransform is not always defined. for (i=0; i<4; i++) { // put the point back in 3D space pt = MathUtils.transformPoint( ptArr[i], planeMat ); // apply the perspective dist = pDist - pt[2]; if (MathUtils.fpSign(dist) !== 0) { scale = pDist / dist; pt[0] *= scale; pt[1] *= scale; pt[2] *= scale; } // add the translation back in pt[0] += midPt[0]; pt[1] += midPt[1]; // to screen coordinates pt = viewUtils.postViewToStageWorld( pt, elt ); pt = vecUtils.vecAdd(3, viewUtils.viewToScreen( MathUtils.transformPoint(pt, stageMat) ), offset ); // save the final result rtnScrPts[i] = pt; } } } });