/* <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> */ /////////////////////////////////////////////////////////////////////// // Class StageLine // The line class represents a line intersected with all planes on the scene /////////////////////////////////////////////////////////////////////// var vecUtils = require("js/helper-classes/3D/vec-utils").VecUtils; var LinePlaneIntersectRec = require("js/helper-classes/3D/LinePlaneIntersectRec").LinePlaneIntersectRec; var StageLine = exports.StageLine = Object.create(Object.prototype, { /////////////////////////////////////////////////////////////////////// // Instance variables /////////////////////////////////////////////////////////////////////// // the 2 points of the line _pt0: { value: null, writable: true }, _pt1: { value: null, writable: true }, // cache the 3D min and max points for the line _minPt: { value: null, writable: true }, _maxPt: { value: null, writable: true }, // the visibility at the start point (this._pt0). _vis: { value: null, writable: true }, // each line/plane intersection records 2 values: the parameter along // the line from pt0 to pt1, and the change in visibility (+1 or -1). we // keep a doubly-linked list of intersection records _intersectionList: { value: null, writable: true }, _intersectionCount: { value: 0, writable: true }, /////////////////////////////////////////////////////////////////////// // Property accessors /////////////////////////////////////////////////////////////////////// getMinPoint: { value: function() { return this._minPt.slice(0); } }, getMaxPoint: { value: function() { return this._maxPt.slice(0); } }, getPoint0: { value: function() { return this._pt0.slice(0); } }, getPoint1: { value: function() { return this._pt1.slice(0); } }, getIntersectionCount: { value: function() { return this._intersectionCount; } }, getIntersectionList: { value: function() { return this._intersectionList; } }, getVisibility: { value: function() { return this._vis; } }, setVisibility: { value: function(v) { this._vis = v; } }, /////////////////////////////////////////////////////////////////////// // Methods /////////////////////////////////////////////////////////////////////// intersectWithPlane: { value: function( plane ) { // if the plane is edge-on, ignore it if ( MathUtils.fpSign( plane.getPlaneEq()[2] ) == 0 ) return; // do some quick box tests. var minPt = this.getMinPoint(), maxPt = this.getMaxPoint(); if (maxPt[0] < plane._rect.getLeft()) return; if (minPt[0] > plane._rect.getRight()) return; if (maxPt[1] < plane._rect.getTop()) return; if (minPt[1] > plane._rect.getBottom()) return; if (minPt[2] > plane.getZMax()) return; // get the boundary points for the plane var boundaryPts = plane.getBoundaryPoints(); // get the points and direction vector for the current line var pt0 = this.getPoint0(), pt1 = this.getPoint1(); //var lineDir = pt1.subtract( pt0 ); var lineDir = vecUtils.vecSubtract(3, pt1, pt0); // intersect with the front plane var planeEq = plane.getPlaneEq(); var t = MathUtils.vecIntersectPlaneForParam( pt0, lineDir, planeEq ); if (t != undefined) { if ((MathUtils.fpSign(t) >= 0) && (MathUtils.fpCmp(t,1.0) <= 0)) { // get the intersection point var pt = MathUtils.interpolateLine3D( pt0, pt1, t ); // see if the intersection point is contained in the bounds //var contains = this.boundaryContainsPoint( boundaryPts, plane.isBackFacing(), pt ); var contains = MathUtils.boundaryContainsPoint( boundaryPts, pt, plane.isBackFacing() ); if (contains == MathUtils.INSIDE) { // add the intersection var dot = MathUtils.dot3( pt0, planeEq ) + planeEq[3]; var deltaVis = (dot > 0) ? 1 : -1; // if (plane.isBackFacing()) // deltaVis = (dot < 0) ? 1 : -1; this.addIntersection( plane, t, deltaVis ); } else if (contains == MathUtils.ON) { if (MathUtils.fpCmp(t,1.0) < 0) { // take the dot product between the line and the normal to the plane // to determine the change in visibility var vec = vecUtils.vecSubtract( 3, pt1, pt0 ); var dot = vecUtils.vecDot( 3, vec, plane.getPlaneEq() ); var sign = MathUtils.fpSign( dot ); if (sign == 0) throw new Error( "coplanar intersection being treated as not coplanar" ); if (!plane.isBackFacing()) { if (sign < 0) this.addIntersection( plane, t, 1 ); } else { if (sign > 0) this.addIntersection( plane, t, -1 ); } } } } } else { // the line must be parallel to the plane. If the line is in the plane, // we need to do some special processing var d0 = vecUtils.vecDot(3, planeEq, pt0) + planeEq[3], d1 = vecUtils.vecDot(3, planeEq, pt1) + planeEq[3]; if ((MathUtils.fpSign(d0) == 0) && (MathUtils.fpSign(d1) == 0)) this.doCoplanarIntersection( plane ); } // intersect with the 4 planes formed by the edges of the plane, going back in Z var bPt1 = boundaryPts[3]; for (var i=0; i<4; i++) { // get the 2 points that define the front edge of the plane var bPt0 = bPt1; var bPt1 = boundaryPts[i]; // calculate the plane equation. The normal should point towards the OUTSIDE of the boundary //var vec = bPt1.subtract( bPt0 ); var vec = vecUtils.vecSubtract(3, bPt1, bPt0); if (plane.isBackFacing()) MathUtils.negate( vec ); planeEq = [-vec[1], vec[0], 0]; var normal = [planeEq[0], planeEq[1], planeEq[2]]; // var d = -planeEq.dot(bPt0); var d = -vecUtils.vecDot(3, planeEq, bPt0); planeEq[3] = d; t = MathUtils.vecIntersectPlaneForParam( pt0, lineDir, planeEq ); if (t) { if ((MathUtils.fpSign(t) > 0) && (MathUtils.fpCmp(t,1.0) <= 0)) // the strict vs not-strict inequality comparisons are IMPORTANT! { // get the intersection point var pt = MathUtils.interpolateLine3D( pt0, pt1, t ); // we need to get the parameter on the edge of the projection // of the intersection point onto the line. var index = (Math.abs(vec[0]) > Math.abs(vec[1])) ? 0 : 1; var tEdge = (pt[index] - bPt0[index])/(bPt1[index] - bPt0[index]); if ((MathUtils.fpSign(tEdge) > 0) && (MathUtils.fpCmp(tEdge,1.0) <= 0)) { var edgePt = MathUtils.interpolateLine3D( bPt0, bPt1, tEdge ); if (MathUtils.fpCmp(pt[2],edgePt[2]) < 0) { // add the intersection var deltaVis = MathUtils.dot(lineDir,normal) > 0 ? -1 : 1; this.addIntersection( plane, t, deltaVis ); } } } } } } }, doCoplanarIntersection: { value: function( plane ) { // get the boundary points for the plane var boundaryPts = plane.getBoundaryPoints(); var planeEq = plane.getPlaneEq(); if (plane.isBackFacing()) { var tmp; tmp = boundaryPts[0]; boundaryPts[0] = boundaryPts[3]; boundaryPts[3] = tmp; tmp = boundaryPts[1]; boundaryPts[1] = boundaryPts[2]; boundaryPts[2] = tmp; } var pt0 = this.getPoint0(), pt1 = this.getPoint1(); // keep a couple flags to prevent counting crossings twice in edge cases var gotEnter = false, gotExit = false; var bp1 = boundaryPts[3]; for (var i=0; i<4; i++) { var bp0 = bp1; bp1 = boundaryPts[i]; var vec = vecUtils.vecSubtract(3, bp1, bp0); var nrm = vecUtils.vecCross(3, vec, planeEq); nrm[3] = -vecUtils.vecDot(3, bp0, nrm); var d0 = vecUtils.vecDot(3, nrm, pt0) + nrm[3], d1 = vecUtils.vecDot(3, nrm, pt1) + nrm[3]; var s0 = MathUtils.fpSign(d0), s1 = MathUtils.fpSign(d1); if (s0 != s1) { var t = Math.abs(d0)/( Math.abs(d0) + Math.abs(d1) ); if ( (MathUtils.fpSign(t) >= 0) && (MathUtils.fpCmp(t,1.0) <= 0)) { // get the point where the line crosses the edge of the element plane var pt = MathUtils.interpolateLine3D(pt0, pt1, t ); // we know that the line crosses the infinite extension of the edge. Determine // if that crossing is within the bounds of the edge var dot0 = vecUtils.vecDot(3, vecUtils.vecSubtract(3,pt, bp0), vec), dot1 = vecUtils.vecDot(3, vecUtils.vecSubtract(3,pt, bp1), vec); if ((MathUtils.fpSign(dot0) > 0) && (MathUtils.fpSign(dot1) < 0)) { // determine if the line is entering or exiting if (s0 <= 0) // entering { if (!gotEnter) { gotEnter = true; this.addIntersection( plane, t, 1 ); } } else if (s0 > 0) // exiting { if (!gotExit) { gotExit = true; this.addIntersection( plane, t, -1 ); } } else // s0 == 0 { // TODO } } else if ((MathUtils.fpSign(dot0) == 0) && (MathUtils.fpSign(dot1) < 0)) { var j = i - 2; if (j < 0) j += 4; var bp = boundaryPts[j]; var v0 = vecUtils.vecSubtract( 3, bp, bp0 ), v1 = vec; if (s0 <= 0) { var v = vecUtils.vecSubtract(3, pt1, pt0); if ((MathUtils.fpSign(vecUtils.vecCross(3, v0,v)) > 0) && (MathUtils.fpSign(vecUtils.vecCross(3, v,v1)) > 0)) { gotEnter = true; this.addIntersection( plane, t, 1 ); } } else if (s0 > 0) { var v = vecUtils.vecSubtract(3, pt0, pt1); // note the reversed order from the previous case if ((MathUtils.fpSign(vecUtils.vecCross(3, v0,v)) > 0) && (MathUtils.fpSign(vecUtils.vecCross(3, v,v1)) > 0)) { gotEnter = true; this.addIntersection( plane, t, -1 ); } } } else if ((MathUtils.fpSign(dot0) > 0) && (MathUtils.fpSign(dot1) == 0)) { var j = (i + 1) % 4; var bp = boundaryPts[j]; var v1 = vec.slice(0), v0 = vecUtils.vecSubtract( 3, bp, bp1 ), v1 = vecUtils.vecNegate(3, v1); if (s0 <= 0) { var v = vecUtils.vecSubtract(3, pt1, pt0); if ((MathUtils.fpSign(vecUtils.vecCross(3, v0,v)) < 0) && (MathUtils.fpSign(vecUtils.vecCross(3, v,v1)) < 0)) { gotEnter = true; this.addIntersection( plane, t, 1 ); } } else if (s0 > 0) { var v = vecUtils.vecSubtract(3, pt0, pt1); // note the reversed order from the previous case if ((MathUtils.fpSign(vecUtils.vecCross(3, v0,v)) > 0) && (MathUtils.fpSign(vecUtils.vecCross(3, v,v1)) > 0)) { gotEnter = true; this.addIntersection( plane, t, -1 ); } } } } } } } }, removeIntersections: { value: function() { this._intersectionList = null; this._intersectionCount = 0; } }, addIntersection: { value: function( plane, t, deltaVis ) { // create the intersection record var iRec = Object.create(LinePlaneIntersectRec, {}); iRec.setStageLine( this ); iRec.setElementPlanes( plane ); iRec.setT( t ); iRec.setDeltaVis( deltaVis ); // the intersection array needs to be sorted by t var ptr = this._intersectionList; var last = null; while (ptr && (t > ptr.getT())) { last = ptr; ptr = ptr.getNext(); } if (ptr == null) { if (last == null) this._intersectionList = iRec; else { last.setNext( iRec ); iRec.setPrev( last ); } } else { if (last != null) { last.setNext( iRec ); iRec.setPrev( last ); } else this._intersectionList = iRec; ptr.setPrev( iRec ); iRec.setNext( ptr ); } this._intersectionCount++; } }, boundaryContainsPoint: { value: function( boundaryPts, backFacing, pt ) { // the computation is done in 2D. // this method returns false if the point is 'on' or outside the boundary var pt1 = boundaryPts[3]; for (var i=0; i<4; i++) { var pt0 = pt1; var pt1 = boundaryPts[i]; //var vec0 = pt1.subtract( pt0 ), // vec1 = pt.subtract( pt0 ); var vec0 = vecUtils.vecSubtract(3, pt1, pt0), vec1 = vecUtils.vecSubtract(pt, pt0); // var cross = vec0.cross( vec1 ); var cross = vecUtils.vecCross(3, vec0, vec1); var inside; if (backFacing) inside = (MathUtils.fpSign(cross[2]) > 0); else inside = (MathUtils.fpSign(cross[2]) < 0); if (!inside) return false; } return true; } }, setPoints: { value: function( pt0, pt1 ) { this._pt0 = pt0.slice(0); this._pt1 = pt1.slice(0); // get the 3D bounds var xMin, xMax, yMin, yMax, zMin, zMax; if (pt0[0] < pt1[0]) { xMin = pt0[0]; xMax = pt1[0]; } else { xMin = pt1[0]; xMax = pt0[0]; } if (pt0[1] < pt1[1]) { yMin = pt0[1]; yMax = pt1[1]; } else { yMin = pt1[1]; yMax = pt0[1]; } if (pt0[2] < pt1[2]) { zMin = pt0[2]; zMax = pt1[2]; } else { zMin = pt1[2]; zMax = pt0[2]; } this._minPt = [xMin, yMin, zMin]; this._maxPt = [xMax, yMax, zMax]; } }//, // getIntersectionParameter: { // value: function( index ) // { // var tRtn; // if (this._paramArray) // { // if ((index >= 0) && (index < this._intersectionCount)) // { // var count = 0; // var iRec = this._intersectionList; // while (count < index) // iRec = iRec.getNext(); // tRtn = iRec.getT(); // } // } // // return tRtn; // } // }, // // getVisibilityChange: { // value: function( index ) // { // var delta; // if ((index >= 0) && (index < this._intersectionCount)) // { // var count = 0; // var iRec = this._intersectionList; // while (count < index) // iRec = iRec.getNext(); // delta = iRec.getDeltaVis(); // } // // return delta; // } // } });