/* <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, Component = require("montage/ui/component").Component, vecUtils = require("js/helper-classes/3D/vec-utils").VecUtils, Rectangle = require("js/helper-classes/3D/rectangle").Rectangle, ElementsMediator = require("js/mediators/element-mediator").ElementMediator; /////////////////////////////////////////////////////////////////////// // Class ViewUtils // Viewing Utility functions /////////////////////////////////////////////////////////////////////// exports.ViewUtils = Montage.create(Component, { /////////////////////////////////////////////////////////////////////// // Instance variables /////////////////////////////////////////////////////////////////////// m_viewportObj : { value: null, writable: true}, _perspectiveDist: { value: null, writable: true}, // keep a stack of viewport objects _viewportObjStack: { value: [], writable: true }, _currentDocument: { value: null , writable: true}, _userContentLeft: { value: null , writable: true}, _userContentTop: { value: null , writable: true}, _rootElement: { value: null, writable: true}, _stageElement: { value: null, writable: true}, /////////////////////////////////////////////////////////////////////// // Property accessors /////////////////////////////////////////////////////////////////////// setViewportObj: { value: function( vp ) { this.m_viewportObj = vp; this._perspectiveDist = this.getPerspectiveDistFromElement( vp ); } }, getViewportObj: { value: function() { return this.m_viewportObj; } }, setRootElement: { value: function( elt ) { this._rootElement = elt; } }, getRootElement: { value: function () { return this._rootElement; } }, setStageElement: { value: function( elt ) { this._stageElement = elt; } }, getStageElement: { value: function () { return this._stageElement; } }, setCurrentDocument: { value: function(value) { this._currentDocument = value; }}, setUserContentLeft: { value: function(value) { this._userContentLeft = value; }}, setUserContentTop: { value: function(value) { this._userContentTop = value; }}, getPerspectiveDistance: { value: function () { return this._perspectiveDist; } }, /////////////////////////////////////////////////////////////////////// // Camera and View Methods /////////////////////////////////////////////////////////////////////// getMatrixFromElement: { value: function( elt ) { var mat = ElementsMediator.getMatrix(elt); if(mat) { return mat; } else { return Matrix.I(4); } } }, setMatrixForElement: { value: function( elt, mat, isChanging ) { ElementsMediator.setMatrix(elt, mat, isChanging); } }, elementHas3D: { value: function( elt ) { return ElementsMediator.has3D(elt); } }, getElementPlane: { value: function( elt ) { var bounds = this.getElementViewBounds3D( elt ); var xArr = new Array(), yArr = new Array(), zArr = new Array(); for (var j=0; j<4; j++) { var pt = this.localToGlobal( bounds[j], elt ); xArr.push(pt[0]); yArr.push(pt[1]); zArr.push(pt[2]); } var normal = MathUtils.getPolygonNormal( 4, xArr, yArr, zArr ); //var d = -MathUtils.dot3( bounds[0], normal ); var d = -MathUtils.dot3( [xArr[0],yArr[0],zArr[0]], normal ); normal[3] = d; return normal; } }, getUnprojectedElementPlane: { value: function( elt ) { var mat = this.getMatrixFromElement(elt); var plane = [mat[8], mat[9], mat[10], mat[11]]; // The translation value is a point on the plane this.pushViewportObj( elt ); var ptOnPlane = this.getCenterOfProjection(); this.popViewportObj(); ptOnPlane[2] = 0; ptOnPlane = this.localToStageWorld(ptOnPlane, elt); plane[3] = -vecUtils.vecDot(3, plane, ptOnPlane ); return plane; } }, getNormalToUnprojectedElementPlane: { value: function( elt ) { var mat = this.getMatrixFromElement(elt); var xVec = [mat[0], mat[1], mat[2], mat[3]]; var yVec = [mat[4], mat[5], mat[6], mat[7]]; var stage = this.application.ninja.currentDocument.documentRoot; var stageMat = this.getMatrixFromElement(stage); var stagePlane = [stageMat[8], stageMat[9], stageMat[10], stageMat[11]]; if (elt === stage) { xVec = [1,0,0]; yVec = [0,1,0]; } var xDot = Math.abs(vecUtils.vecDot(3, xVec, stagePlane)); var yDot = Math.abs(vecUtils.vecDot(3, yVec, stagePlane)); var plane; if(xDot > yDot) { plane = xVec; } else { plane = yVec; } // The translation value is a point on the plane this.pushViewportObj( elt ); var ptOnPlane = this.getCenterOfProjection(); this.popViewportObj(); ptOnPlane[2] = 0; ptOnPlane = this.localToStageWorld(ptOnPlane, elt); plane[3] = -vecUtils.vecDot(3, plane, ptOnPlane ); return plane; } }, getPerspectiveModeFromElement: { value: function( elt ) { return ElementsMediator.getPerspectiveMode(elt); } }, getPerspectiveDistFromElement: { value: function( elt ) { return ElementsMediator.getPerspectiveDist(elt); } }, getEyePoint: { value:function() { // this function should use the center of projection - it is currently hard wired to (0,0). var eye = [0, 0, this._perspectiveDist]; return eye; } }, getCameraMatrix: { value: function() { return this.getMatrixFromElement( this.m_viewportObj ); } }, getElementViewBounds3D: { value: function( elt ) { var bounds = this.getElementBounds( elt, true ); var ptArray = new Array(); for (var i=0; i<4; i++) { var pt = bounds.getPoint( i ); pt[2] = 0; // z == 0 ptArray.push( pt ); } return ptArray; } }, getCenterOfProjection: { value: function() { var cop; var vp = this.getViewportObj(); if (vp) { var bounds = this.getViewportBounds(); cop = bounds.getCenter(); //cop = [bounds.getRight(), bounds.getBottom()]; } return cop; } }, preToPostScreen: { value: function( pt, elt ) { this.pushViewportObj( elt ); var viewPt = this.screenToView( pt[0], pt[1], pt[2] ); var mat = this.getMatrixFromElement( elt ); var worldPt = MathUtils.transformPoint( viewPt, mat ); var screenPt = this.viewToScreen( worldPt ); this.popViewportObj(); return screenPt; } }, localToStageWorld: { value: function( localPt, elt ) { this.pushViewportObj( elt ); var viewPt = this.screenToView( localPt[0], localPt[1], localPt[2] ); var mat = this.getMatrixFromElement( elt ); var worldPt = MathUtils.transformPoint( viewPt, mat ); var stageWorldPt = this.postViewToStageWorld( worldPt, elt ); this.popViewportObj(); return stageWorldPt; } }, // "post view" refers to a point in view space after the transform // i.e., pre and post view spaces. // this function is used by snapping routines to put element snap positions // into stage world space. postViewToStageWorld: { value: function( localPt, elt ) { if ((elt == null) || (elt === this._stageElement)) return localPt; // get the 3D transformation and 2D offset from the element var pt = localPt.slice(0); pt = MathUtils.makeDimension3( pt ); // transform the point up the tree var child = elt; var parent = elt.offsetParent; while ( parent ) { // go to screen space of the current child this.pushViewportObj( child ); pt = this.viewToScreen( pt ); this.popViewportObj(); // to screen space of the parent var offset = this.getElementOffset( child ); offset[2] = 0.0; pt = vecUtils.vecAdd( 3, pt, offset ); // to view space of the parent this.pushViewportObj( parent ); pt = this.screenToView( pt[0], pt[1], pt[2] ); this.popViewportObj(); // check if we are done if (parent === this._stageElement) break; if (this.elementHas3D( parent )) { var parentMat = this.getMatrixFromElement( parent ); pt = MathUtils.transformPoint( pt, parentMat ); } child = parent; parent = parent.offsetParent; } return pt; } }, localToGlobal: { value: function( localPt, elt ) { // get the 3D transformation and 2D offset from the element var pt = localPt.slice(0); if (pt.length < 3) pt[2] = 0; if (pt.length == 4) pt.pop(); // transform the bounds up the tree var child = elt; var parent = elt.offsetParent; while ( parent ) { pt = this.childToParent( pt, child ); if (parent === this._rootElement) break; child = parent; parent = parent.offsetParent; } ///////////////////////////////////////////////////////// // DEBUG CODE /* var tmpMat = this.getLocalToGlobalMatrix( elt ); var hPt = localPt.slice(0); MathUtils.makeDimension4( hPt ); var tmpPt = MathUtils.transformHomogeneousPoint( hPt, tmpMat ); var gPt = MathUtils.applyHomogeneousCoordinate( tmpPt ); */ ///////////////////////////////////////////////////////// return pt; } }, localToGlobal2: { value: function( localPt, tmpMat ) { var hPt = localPt.slice(0); MathUtils.makeDimension4( hPt ); var tmpPt = MathUtils.transformHomogeneousPoint( hPt, tmpMat ); var gPt = MathUtils.applyHomogeneousCoordinate( tmpPt ); gPt = MathUtils.makeDimension3( gPt ); return gPt; } }, childToParent: { value: function( pt, child ) { // pt should be a 3D (2D is ok) vector in the space of the element if (pt.length == 2) pt[2] = 0; // transform the bounds up the tree var parent = child.offsetParent; if ( parent ) { this.setViewportObj( child ); // get the offset (previously computed var childMat = this.getMatrixFromElement( child ); var offset = this.getElementOffset( child ); offset[2] = 0; if (this.elementHas3D( child )) { // TODO - Commenting out flatten support until new perspective workflow is fully working // if (flatten) pt[2] = 0; // var flatten = (parent !== this._rootElement) && (ElementsMediator.getProperty(parent, "-webkit-transform-style") !== "preserve-3d"); // if(flatten) // { // pt[2] = 0; // } pt = this.screenToView( pt[0], pt[1], pt[2] ); pt[3] = 1; //var wPt = childMat.multiply( pt ); var wPt = glmat4.multiplyVec3( childMat, pt, [] ); // if(flatten) // { // wPt[2] = 0; // } var scrPt = this.viewToScreen( wPt ); pt = scrPt; } //pt = pt.add(offset); pt = vecUtils.vecAdd(3, pt, offset); } return [pt[0], pt[1], pt[2]]; } }, viewToParent: { value: function( viewPt, child ) { // pt should be a 3D (2D is ok) vector in the space of the element var pt = viewPt.slice(0); if (pt.length == 2) pt[2] = 0; pt[3] = 1; // transform the bounds up the tree var parent = child.offsetParent; if ( parent ) { this.setViewportObj( child ); // get the offset (previously computed var offset = this.getElementOffset( child ); offset[2] = 0; pt = this.viewToScreen( pt ); //pt = pt.add(offset); pt = vecUtils.vecAdd(3, pt, offset); } return [pt[0], pt[1], pt[2]]; } }, /** * The input plane is specified in stage world space. * The output plane is specified in the world space of the input element. **/ globalPlaneToLocal: { value: function( plane, elt ) { // get the four corners of the element in global space var bounds = this.getElementViewBounds3D( elt ); var bounds3D = new Array(); var stage = this.application.ninja.currentDocument.documentRoot; for (var i=0; i<3; i++) { var gPt = this.localToGlobal( bounds[i], elt ); bounds3D[i] = this.parentToChildWorld( gPt, stage ); } /* this.pushViewportObj( elt ); var parent = elt.offsetParent; var offset = this.getElementOffset( elt ); offset[2] = 0; var localEyePt = this.getCenterOfProjection(); localEyePt[2] = this.getPerspectiveDistance(); localEyePt = vecUtils.vecAdd( 3, offset, localEyePt ); var eyePt = this.localToGlobal( localEyePt, parent ); eyePt = this.parentToChildWorld( eyePt, stage ); */ // drop the 4 corner points onto the plane and convert back to local space var ptArray = new Array; for (var i=0; i<3; i++) { var planePt = MathUtils.vecIntersectPlane( bounds3D[i], workingPlane, workingPlane ); this.setViewportObj( stage ); var viewPlanePt = this.viewToScreen( planePt ); var globalPlanePt = this.localToGlobal( viewPlanePt, stage ); var localPlanePt = this.globalToLocal( globalPlanePt, elt ); ptArray.push( localPlanePt ); } // get the plane from the 3 points var vec0 = vecUtils.vecSubtract(3, ptArray[0], ptArray[1] ), vec1 = vecUtils.vecSubtract(3, ptArray[2], ptArray[1] ); var normal = MathUtils.cross( vec0, vec1 ); var mag = vecUtils.vecMag(3, normal ); if (MathUtils.fpSign(mag) == 0) { } var localPlane = vecUtils.vecNormalize( 3, normal, 1.0 ); localPlane[3] = -vecUtils.vecDot( 3, ptArray[0], localPlane ); // this.popViewportObj(); return localPlane; } }, parentToChild: { value: function( parentPt, child, passthrough ) { var pt = parentPt.slice(0); if (pt.length == 2) pt[2] = 0.0; // subtract off the the offset var offset = this.getElementOffset( child ); offset[2] = 0.0; pt = vecUtils.vecSubtract( 3, pt, offset ); // put the point in the view space of the child this.setViewportObj( child ); var viewPt = this.screenToView( pt[0], pt[1], pt[2] ); // find the plane to project to var mat = this.getMatrixFromElement( child ); var plane = MathUtils.transformPlane( [0,0,1,0], mat ); // project the view point onto the plane var eyePt; if(this.getPerspectiveDistFromElement(child)) { eyePt = this.getEyePoint(); } else { eyePt = [viewPt[0], viewPt[1], 1400]; } var projPt = MathUtils.vecIntersectPlane( eyePt, MathUtils.vecSubtract(viewPt,eyePt), plane ); var childPt; if (passthrough) { //var inv = mat.inverse(); var inv = glmat4.inverse( mat, []); var invPt = MathUtils.transformPoint( projPt, inv ); // put into screen space (without projecting) childPt = this.viewToScreen( invPt ); } else { childPt = this.viewToScreen( projPt ); } return childPt; } }, parentToChildWorld: { value: function( parentPt, child ) { var pt = parentPt.slice(0); if (pt.length == 2) pt[2] = 0.0; // subtract off the the offset var offset = this.getElementOffset( child ); offset[2] = 0.0; //pt = pt.subtract( offset ); pt = vecUtils.vecSubtract(3, pt, offset); // put the point in the view space of the child this.pushViewportObj( child ); var viewPt = this.screenToView( pt[0], pt[1], pt[2] ); // find the plane to project to var mat = this.getMatrixFromElement( child ); var plane = MathUtils.transformPlane( [0,0,1,0], mat ); // project the view point onto the plane var eyePt; if(this.getPerspectiveDistFromElement(child)) { eyePt = this.getEyePoint(); } else { eyePt = [viewPt[0], viewPt[1], 1400]; } var projPt = MathUtils.vecIntersectPlane( eyePt, MathUtils.vecSubtract(viewPt,eyePt), plane ); this.popViewportObj(); return projPt; } }, parentToChildVec: { value: function( parentPt, child, rtnEyePt ) { var pt = parentPt.slice(0); if (pt.length == 2) pt[2] = 0.0; // subtract off the the offset var offset = this.getElementOffset( child ); offset[2] = 0.0; pt = vecUtils.vecSubtract( 3, pt, offset ); // put the point in the view space of the child this.setViewportObj( child ); pt = this.screenToView( pt[0], pt[1], pt[2] ); var eyePt; if(this.getPerspectiveDistFromElement(child)) { eyePt = this.getEyePoint(); } else { eyePt = [pt[0], pt[1], 1400]; } var vec = vecUtils.vecSubtract(3, [pt[0], pt[1], pt[2]], eyePt); vec = vecUtils.vecNormalize( 3, vec ); if(rtnEyePt) { rtnEyePt[0] = eyePt[0]; rtnEyePt[1] = eyePt[1]; rtnEyePt[2] = eyePt[2]; } return vec; } }, getElementBounds: { value: function( elt, localSpace ) { // optional argument localSpace, if true, puts the top left at (0,0). if (arguments.length < 2) localSpace = false; var bounds; var left = elt.offsetLeft, top = elt.offsetTop, w = elt.offsetWidth, h = elt.offsetHeight; // if (elt instanceof SVGSVGElement) { if(elt.nodeName.toLowerCase() === "svg") { if(w instanceof SVGAnimatedLength) w = w.animVal.value; if(h instanceof SVGAnimatedLength) h = h.animVal.value; } bounds = Object.create(Rectangle, {}); if (localSpace) { left = 0; top = 0; } bounds.set( left, top, w, h ); return bounds; } }, getViewportBounds: { value: function() { var bounds; var vp = this.m_viewportObj; if (vp) { bounds = this.getElementBounds( vp ); bounds.setLeft(0); bounds.setTop(0); } return bounds; } }, getElementOffset: { value: function( elt ) { var xOff = elt.offsetLeft, yOff = elt.offsetTop; // if (elt.__ninjaXOff) xOff = elt.__ninjaXOff; // if (elt.__ninjaYOff) yOff = elt.__ninjaYOff; var offset = [xOff, yOff]; if(elt.offsetParent && (elt.offsetParent !== this._stageElement)) { var pS = elt.ownerDocument.defaultView.getComputedStyle(elt.offsetParent); var border = parseInt(pS.getPropertyValue("border")); if(border) { var bl = parseInt(pS.getPropertyValue("border-left-width")), bt = parseInt(pS.getPropertyValue("border-top-width")); offset[0] += bl; offset[1] += bt; } } if(elt === this._stageElement) { // TODO - Call a routine from the user document controller to get the offsets/margins // Once we expose the document controller to ViewUtils offset[0] += this._userContentLeft; offset[1] += this._userContentTop; } return offset; } }, getCameraPos: { value: function() { var cameraPos = [0, 0, this._perspectiveDist]; return cameraPos; } }, getZIndex: { value: function( elt ) { var zIndex = 0; if (elt.style.zIndex) zIndex = Number( elt.style.zIndex ); return zIndex; } }, setZIndex: { value: function( elt, zIndex ) { elt.style.zIndex = zIndex; } }, screenToView: { value: function(xScr, yScr, zScr) { if (arguments.length < 3) zScr = 0; var ctr = this.getCenterOfProjection(); var xCtr = ctr[0], yCtr = ctr[1]; // var bounds = this.getViewportBounds(); // var xCtr = bounds.getLeft() + 0.5*bounds.getWidth(), // yCtr = bounds.getTop() + 0.5*bounds.getHeight(); // perspective origin not supported /* if (viewportObj.style.webkit-perspective-origin) { } */ //var yView = yCtr - yScr, var yView = yScr - yCtr, xView = xScr - xCtr, zView = zScr; return [xView, yView, zView]; } }, viewToScreen: { value: function( viewPoint ) { var scrPt; var viewport = this.m_viewportObj; if (viewport) { // project the point to the z=0 plane var viewPt = this.projectToViewPlane( viewPoint ); // convert from view to screen space // var bounds = this.getViewportBounds(); // var ctr = bounds.getCenter(); var ctr = this.getCenterOfProjection(); scrPt = [ctr[0] + viewPt[0], ctr[1] + viewPt[1], viewPt[2]]; } return scrPt; } }, projectToViewPlane: { value: function( viewPos ) { if(!this._perspectiveDist) { return viewPos.slice(0); } var viewPt; var viewport = this.m_viewportObj; if (viewport) { viewPt = [viewPos[0], viewPos[1], viewPos[2]]; // apply the perspective var dist = this._perspectiveDist - viewPt[2]; var scale = 0.0; if (MathUtils.fpSign(dist) != 0) { scale = this._perspectiveDist / dist; viewPt[0] *= scale; viewPt[1] *= scale; viewPt[2] *= scale; } else console.log( "***** infinite projection *****" ); } return viewPt; } }, unproject: { value: function( pt ) { if(!this._perspectiveDist) { return pt.slice(0); } var viewPt; var viewport = this.m_viewportObj; if (viewport) { viewPt = pt.slice(0); MathUtils.makeDimension3( viewPt ); // calculate the unprojected Z value var p = this._perspectiveDist; var zp = viewPt[2]; // zp == z projected var z = zp*p/(zp + p); // z == unprojected z value var s = (p - z)/p; var x = viewPt[0] * s, y = viewPt[1] * s; viewPt[0] = x; viewPt[1] = y; viewPt[2] = z; } return viewPt; } }, worldToView: { value: function( worldPos ) { // we have no camera, always the same point return worldPos; } }, viewToWorld: { value: function( viewPos ) { // we have no camera, always the same point return viewPos; } }, worldToScreen: { value: function( worldPos ) { var scrPt; var viewport = this.m_viewportObj; if (viewport) { var viewPt = this.worldToView( worldPos ) if (viewPt) scrPt = this.viewToScreen( viewPt ); } return scrPt; } }, getStageWorldToGlobalMatrix: { value: function() { var stage = this.application.ninja.currentDocument.documentRoot; this.pushViewportObj( stage ); // put the point into screen space of the stage - requires // a translation to the top/left only var cop = this.getCenterOfProjection(); var v2s = Matrix.Translation([cop[0], cop[1], 0]); this.popViewportObj(); // append the localToGlobal matrix of the stage. var mat = this.getLocalToGlobalMatrix( stage ); glmat4.multiply( mat, v2s ); return mat; } }, localScreenToLocalWorld: { value: function( objPt, elt ) { MathUtils.makeDimension3( objPt ); this.pushViewportObj( elt ); var viewPt = this.screenToView( objPt[0], objPt[1], objPt[2] ); this.popViewportObj(); var mat = this.getMatrixFromElement( elt ); viewPt = MathUtils.transformPoint( viewPt, mat ); return viewPt; } }, globalScreenToLocalWorld: { value: function( globalPt, elt ) { var objPt = this.globalToLocal( globalPt, elt ); var viewPt = this.localScreenToLocalWorld( objPt, elt ); /* MathUtils.makeDimension3( objPt ); this.pushViewportObj( elt ); var viewPt = this.screenToView( objPt[0], objPt[1], objPt[2] ); this.popViewportObj(); var mat = this.getMatrixFromElement( elt ); viewPt = MathUtils.transformPoint( viewPt, mat ); */ return viewPt; } }, globalToLocal: { value: function( targetScrPt, elt ) { var objPt; // get matrix going from object local to screen space, and it's inverse var o2w = this.getLocalToGlobalMatrix( elt ); //var w2o = o2w.inverse(); var w2o = glmat4.inverse( o2w, []); // transform the input point in screen space to object space var tmpInPt = targetScrPt.slice(0); tmpInPt = MathUtils.makeDimension4( tmpInPt ); tmpInPt[2] = 0.0; // z == 0 var tmpPt1 = MathUtils.transformHomogeneousPoint( tmpInPt, w2o); tmpPt1 = MathUtils.applyHomogeneousCoordinate( tmpPt1 ); // get a second point in object space starting from the input point plus an (arbitrary) z offset tmpInPt[2] = 100.0; var tmpPt2 = MathUtils.transformHomogeneousPoint( tmpInPt, w2o); tmpPt2 = MathUtils.applyHomogeneousCoordinate( tmpPt2 ); // project the 2 object space points onto the original plane of the object objPt = MathUtils.vecIntersectPlane( tmpPt1, vecUtils.vecSubtract(3, tmpPt2, tmpPt1), [0,0,1,0] ); return objPt; } }, getLocalToGlobalMatrix: { value: function( elt ) { var mat = Matrix.I(4), projMat, pDist; // TODO - Commenting out flatten support until new perspective workflow is fully working var zMat = Matrix.I(4); // zMat[9] = 0; // zMat[10] = 0; // zMat[11] = 0; // zMat[12] = 0; while (elt) { this.pushViewportObj( elt ); var cop = this.getCenterOfProjection(); var s2v = Matrix.Translation([-cop[0], -cop[1], 0]); var objMat = this.getMatrixFromElement( elt ); //var projMat = Matrix.I(4).multiply( this.getPerspectiveDistFromElement(elt) ); pDist = this.getPerspectiveDistFromElement(elt); if(pDist) { projMat = glmat4.scale(Matrix.I(4), [pDist,pDist,pDist], []); projMat[11] = -1; projMat[15] = 1400; } var v2s = Matrix.Translation([cop[0], cop[1], 0]); glmat4.multiply( s2v, mat, mat ); glmat4.multiply( objMat, mat, mat ); // glmat4.multiply( projMat, mat, mat ); if(pDist) { //mat = projMat.multiply( mat ); glmat4.multiply( projMat, mat, mat ); pDist = null; } glmat4.multiply( v2s, mat, mat ); // TODO - Commenting out flatten support until new perspective workflow is fully working // var flatten = (elt !== this._rootElement) && (elt.parentElement !== this._rootElement) && (ElementsMediator.getProperty(elt.parentElement, "-webkit-transform-style") !== "preserve-3d"); // if(flatten) // { // glmat4.multiply( zMat, mat, mat ); // } // offset to the parent var offset = this.getElementOffset( elt ); var offMat = Matrix.Translation([offset[0], offset[1], 0]); //mat = offMat.multiply( mat ); glmat4.multiply( offMat, mat, mat ); this.popViewportObj(); if (elt === this._stageElement) break; if (elt === this._rootElement) break; elt = elt.offsetParent; if (elt === this._rootElement) break; } return mat; } }, getObjToStageWorldMatrix: { value: function( elt, shouldProject ) { var mat = Matrix.I(4), projMat, pDist; while (elt) { this.pushViewportObj( elt ); var cop = this.getCenterOfProjection(); var s2v = Matrix.Translation([-cop[0], -cop[1], 0]); var objMat = this.getMatrixFromElement( elt ); if(shouldProject) { pDist = this.getPerspectiveDistFromElement(elt); if(pDist) { projMat = glmat4.scale(Matrix.I(4), [pDist,pDist,pDist], []); projMat[11] = -1; projMat[15] = 1400; } } var v2s = Matrix.Translation([cop[0], cop[1], 0]); this.popViewportObj(); // multiply all the matrices together //mat = s2v.multiply( mat ); glmat4.multiply( s2v, mat, mat ); if (elt === this._stageElement) break; //mat = objMat.multiply( mat ); glmat4.multiply( objMat, mat, mat ); if(shouldProject && pDist) { //mat = projMat.multiply( mat ); glmat4.multiply( projMat, mat, mat ); pDist = null; } //mat = v2s.multiply( mat ); glmat4.multiply( v2s, mat, mat ); // offset to the parent var offset = this.getElementOffset( elt ); var offMat = Matrix.Translation([offset[0], offset[1], 0]); //mat = offMat.multiply( mat ); glmat4.multiply( offMat, mat, mat ); elt = elt.offsetParent; } return mat; } }, getLocalToStageWorldMatrix: { value: function( elt, shouldProject, shouldLocalTransform ) { var mat = Matrix.I(4); while (elt) { this.pushViewportObj( elt ); var cop = this.getCenterOfProjection(); var s2v = Matrix.Translation([-cop[0], -cop[1], 0]); var objMat = this.getMatrixFromElement( elt ); var projMat; if(shouldProject) { //projMat = Matrix.I(4).multiply( this.getPerspectiveDistFromElement(elt) ); var pDist = this.getPerspectiveDistFromElement(elt); var projMat = glmat4.scale(Matrix.I(4), [pDist,pDist,pDist], []); projMat[11] = -1; projMat[15] = 1400; } var v2s = Matrix.Translation([cop[0], cop[1], 0]); this.popViewportObj(); // multiply all the matrices together //mat = s2v.multiply( mat ); glmat4.multiply( s2v, mat, mat ); if (elt === this._stageElement) break; //mat = objMat.multiply( mat ); if (shouldLocalTransform) { glmat4.multiply( objMat, mat, mat ); } if(shouldProject) { //mat = projMat.multiply( mat ); glmat4.multiply( projMat, mat, mat ); } //mat = v2s.multiply( mat ); glmat4.multiply( v2s, mat, mat ); // offset to the parent var offset = this.getElementOffset( elt ); var offMat = Matrix.Translation([offset[0], offset[1], 0]); //mat = offMat.multiply( mat ); glmat4.multiply( offMat, mat, mat ); elt = elt.parentElement; } return mat; } }, getUpVectorFromMatrix: { value: function( mat ) { //var inv = mat.inverse(); var yAxis = [mat[4], mat[5], mat[6]]; yAxis = vecUtils.vecNormalize( 3, yAxis ); return yAxis; } }, getRollVectorFromMatrix: { value: function( mat ) { //var inv = mat.inverse(); var zAxis = [mat[8], mat[9], mat[10]]; zAxis = vecUtils.vecNormalize( 3, zAxis ); return zAxis; } }, getMatrixFromVectors: { value: function( upVec, zVec ) { // get the set of 3 orthogonal axes var yAxis = upVec.slice(0); MathUtils.makeDimension3( yAxis ); yAxis = vecUtils.vecNormalize( 3, yAxis ); var zAxis = zVec.slice(0); MathUtils.makeDimension3( zAxis ); zAxis = vecUtils.vecNormalize( 3, zAxis ); var xAxis = MathUtils.cross( yAxis, zAxis ); var dot = MathUtils.dot3( yAxis, zAxis ); if (MathUtils.fpSign(dot) != 0) console.log( "axes not orthogonal" ); // create the matrix var mat = Matrix.create( [ [xAxis[0], yAxis[0], zAxis[0], 0], [xAxis[1], yAxis[1], zAxis[1], 0], [xAxis[2], yAxis[2], zAxis[2], 0], [ 0, 0, 0, 1] ] ); return mat; } }, createMatrix: { value: function( startMat, startMatInv, ctr, upVec, zAxis, azimuth, altitude ) { //console.log( "upVec: " + upVec + ", zAxis: " + zAxis + ", azimuth: " + azimuth + ", altitude: " + altitude ); var yMat = Matrix.RotationY( azimuth ), xMat = Matrix.RotationX( altitude ); //mat = yMat.multiply( xMat ); var mat = glmat4.multiply( yMat, xMat, []); // apply the 'up' matrix var upMat = this.getMatrixFromVectors( upVec, zAxis ); //this.checkMat( upMat ); //mat = upMat.multiply( mat ); glmat4.multiply( upMat, mat, mat ); // apply the start matrix //mat = mat.multiply( startMatInv ); //mat = startMat.multiply( mat ); if(ctr) { // pre-translate by the negative of the transformation center var tMat = Matrix.I(4); tMat[12] = -ctr[0]; tMat[13] = -ctr[1]; tMat[14] = -ctr[2]; //mat = mat.multiply( tMat ); glmat4.multiply( mat, tMat ); // translate back to the transform center tMat[12] = ctr[0]; tMat[13] = ctr[1]; tMat[14] = ctr[2]; //mat = tMat.multiply( mat ); glmat4.multiply( tMat, mat, mat ); } //this.checkMat( mat ); //this.printMat( mat ); return mat; } }, printMat: { value: function( mat ) { console.log( "\tmat: " + mat ); // console.log( "\t\t" + mat[0] ); // console.log( "\t\t" + mat[1] ); // console.log( "\t\t" + mat[2] ); } }, checkMat: { value: function( mat ) { var xAxis = [mat[0], mat[1], mat[ 2]], yAxis = [mat[4], mat[5], mat[ 6]], zAxis = [mat[8], mat[9], mat[10]]; var xMag = MathUtils.vecMag3( xAxis ), yMag = MathUtils.vecMag3( yAxis ), zMag = MathUtils.vecMag3( zAxis ); if (MathUtils.fpCmp(xMag,1) != 0) console.log( "xAxis not unit length: " + xMag ); if (MathUtils.fpCmp(yMag,1) != 0) console.log( "yAxis not unit length: " + yMag ); if (MathUtils.fpCmp(zMag,1) != 0) console.log( "zAxis not unit length: " + zMag ); /* var dot = MathUtils.dot3( xAxis, yAxis ); if (MathUtils.fpSign(dot) != 0) console.log( "X-Y not orthogonal" ); dot = MathUtils.dot3( xAxis, zAxis ); if (MathUtils.fpSign(dot) != 0) console.log( "X-Z not orthogonal" ); dot = MathUtils.dot3( yAxis, zAxis ); if (MathUtils.fpSign(dot) != 0) console.log( "Y-Z not orthogonal" ); */ } }, pushViewportObj: { value: function( obj ) { this.setViewportObj(obj); this._viewportObjStack.push( obj ); } }, popViewportObj: { value: function() { if (this._viewportObjStack.length == 0) { throw( "viewport object stack underflow" ); return; } var rtn = this.m_viewportObj; this.setViewportObj(this._viewportObjStack.pop()); return rtn; } }, /////////////////////////////////////////////////////////////////////////////////// // Montage update map // // NO LONGER SUPPORTED: // stageManagerModule // drawLayoutModule // // STAGE ACCESSORS: // activeDocument: this.application.ninja.currentDocument // userContent (stage): this.application.ninja.currentDocument.documentRoot // stageManager: this.application.ninja.stage // MainApp\js\stage\stage.reel\stage.js // stageManager._canvas: this.application.ninja.stage.canvas // stageManager.layoutCanvas: this.application.ninja.stage.layoutCanvas // stageManager.drawingCanvas: this.application.ninja.stage.drawingCanvas // stageManager.userContentLeft this.application.ninja.stage.userContentLeft // viewUtils: stage.viewUtils; // snapManager stage.snapManager; // // REDRAW FUNCTIONS // window.stageManager.drawSelectionRec(true): this.application.ninja.stage.updateStage = true; // drawLayoutModule.drawLayout.redrawDocument() OR // window.stageManager.drawSelectionRec(true) this.getStage().draw(); // drawLayoutModule.drawLayout.redrawDocument(); // SELECTION MANAGER // selected elements: this.application.ninja.selectedElements // selectionManager this.application.ninja.selectionController // selected elements: this.application.ninja.selectionController.selectElements // // MISCELLANEOUS // event.layerX/Y: var pt = viewUtils.getMousePoint(event); getStageDimension: { value: function() { var width = parseInt(this.application.ninja.stage.documentRoot.elementModel.stageDimension.style.getProperty("width")); var height= parseInt(this.application.ninja.stage.documentRoot.elementModel.stageDimension.style.getProperty("height")); return[width,height]; } }, getStage: { value: function() { return this.application.ninja.stage.snapManager.getStage(); } }, clearStageTranslation: { value: function() { if (this.application.ninja.currentDocument) { // get the user content object var userContent = this.application.ninja.currentDocument.documentRoot; if (!userContent) return; this.setViewportObj( userContent ); // calculate the new matrix var ucMat = this.getMatrixFromElement(userContent); var targetMat = ucMat.slice(); targetMat[12] = 0; targetMat[13] = 0; targetMat[14] = 0; var ucMatInv = glmat4.inverse( ucMat, [] ); var deltaMat = glmat4.multiply( targetMat, ucMatInv, [] ); this.setMatrixForElement(userContent, targetMat ); } } }, getCurrentDocument: { value: function() { return snapManagerModule.SnapManager.application.ninja.currentDocument; } }, setStageZoom: { value:function( globalPt, zoomFactor ) { var localPt; var tmp1, tmp2, tmp3; if (this.application.ninja.currentDocument) { var userContent = this.application.ninja.currentDocument.documentRoot; if (!userContent) return; this.setViewportObj( userContent ); var userContentMat = this.getMatrixFromElement(userContent); // create a matrix to do the scaling // the input zoomFactor is the total zoom for the resulting matrix, so we need to // get the current scale from the existing userContent matrix // we assume a uniform scale. var ucX = [userContentMat[0], userContentMat[1+0], userContentMat[2+0]]; var ucY = [userContentMat[4], userContentMat[1+4], userContentMat[2+4]]; var ucZ = [userContentMat[8], userContentMat[1+8], userContentMat[2+8]]; var sx = vecUtils.vecMag(3, ucX), sy = vecUtils.vecMag(3, ucY), sz = vecUtils.vecMag(3, ucZ); if ((MathUtils.fpCmp(sx,sy) != 0) || (MathUtils.fpCmp(sx,sz)) != 0) console.log( "**** non-uniform scale in view matrix **** " + sx + ", " + sy + ", " + sz ); var newZoomFactor = zoomFactor/sx; // console.log( "old zoom: " + zoomFactor + ", new zoom: " + newZoomFactor ); if (MathUtils.fpCmp(newZoomFactor,1.0) == 0) console.log( "no zoom applied" ); else { var zoomMat=[newZoomFactor,0,0,0,0,newZoomFactor,0,0,0,0,newZoomFactor,0,0,0,0,1]; // get a point in local userContent space localPt = this.globalToLocal( globalPt, userContent ); var scrPt = this.screenToView( localPt[0], localPt[1], localPt[2] ); var worldPt = MathUtils.transformPoint( scrPt, userContentMat ); tmp1 = this.localToGlobal( localPt, userContent ); // DEBUG - remove this line // set the viewport object this.setViewportObj( userContent ); // scale around the world point to give the same screen location var transPt = worldPt.slice(); var transPtNeg = transPt.slice(); vecUtils.vecNegate( 3, transPtNeg ); var trans1 = Matrix.Translation( transPtNeg ), trans2 = Matrix.Translation( transPt ); var mat = glmat4.multiply( zoomMat, trans1, [] ); glmat4.multiply( trans2, mat, mat ); var newUCMat = glmat4.multiply( mat, userContentMat, []); this.setMatrixForElement(userContent, newUCMat ); tmp2 = this.localToGlobal( localPt, userContent ); // DEBUG - remove this line // apply to the stage background // var stageBG = this.application.ninja.currentDocument.stageBG; // var stageBGMat = this.getMatrixFromElement(stageBG); // var newStageBGMat = glmat4.multiply( mat, stageBGMat, []); // this.setMatrixForElement(stageBG, newStageBGMat ); } } } }, getCanvas: { value: function() { return this.application.ninja.stage.canvas; } }, getSelectionManager: { value: function() { return this.application.ninja.selectionController; } }, getSelectedElements: { value: function() { return this.application.ninja.selectedElements.slice(); } }, getMousePoint: { value: function(event) { var point = webkitConvertPointFromPageToNode(this.getCanvas(), new WebKitPoint(event.pageX, event.pageY)); return [point.x, point.y]; } } /////////////////////////////////////////////////////////////////////////////////// });