// // Copyright 2006-2007, 2015 Adobe Systems, Incorporated. All Rights Reserved. // /* @@@BUILDINFO@@@ Geometry.jsx 1.0.0.0 */ // // Geometry definitions, based on PMGeometry.h // function className( thing ) { if ((typeof(thing) == "object") && (typeof(thing.className) != "undefined")) return thing.className; else return typeof(thing); } // // =================================== TPoint =================================== // function TPoint( x, y ) { this.fX = x; this.fY = y; } // TPoint Constants const kTPointOrigion = new TPoint( 0, 0 ); TPoint.kOrigin = kTPointOrigion; const kTPointInfinite = new TPoint( Infinity, Infinity ); TPoint.kInfinite = kTPointInfinite; const kTPointClassname = "TPoint"; TPoint.prototype.className = kTPointClassname; // Overloaded math operators TPoint.prototype["=="] = function( Src ) { return (this.fX == Src.fX) && (this.fY == Src.fY); } TPoint.prototype["==="] = function( Src ) { return (this.fX === Src.fX) && (this.fY === Src.fY); } TPoint.prototype["+"] = function( b ) { return new TPoint( this.fX + b.fX, this.fY + b.fY ); } TPoint.prototype["-"] = function( b, reversed ) { if (typeof(b) == "undefined") // unary minus return new TPoint( -this.fX, -this.fY ) else { if (reversed) return new TPoint( b.fX - this.fX, by.fY - this.fY ); else return new TPoint( this.fX - b.fX, this.fY - b.fY); } } // // Multiply and divide work with scalars as well as points // TPoint.prototype["*"] = function( b ) { if (typeof(b) == 'number') return new TPoint( this.fX * b, this.fY * b ); else return new TPoint( this.fX * b.fX, this.fY * b.fY ); } TPoint.prototype["/"] = function( b, reversed ) { if (reversed) { if (typeof(b) == "number") debugger; // Can't divide a number by a point else return new TPoint( b.fX / this.fX, b.fY / this.fY ); } else { if (typeof(b) == 'number') return new TPoint( this.fX / b, this.fY / b ); else return new TPoint( this.fX / b.fX, this.fY / b.fY ); } } TPoint.prototype.toString = function() { return "[" + this.fX.toString() + "," + this.fY.toString() + "]"; } TPoint.prototype.vectorLength = function() { return Math.sqrt( this.fX * this.fX + this.fY * this.fY ); } TPoint.prototype.distanceTo = function(p) { var delta = this - p; return delta.vectorLength(); } // Return normalized vector TPoint.prototype.normalize = function() { var vecLen = this.vectorLength(); if (vecLen == 0.0) return new TPoint( 0.0, 0.0 ); else return new TPoint( this.fX / vecLen, this.fY / vecLen ); } // Return the angle between the vector from the origin to this and the X axis TPoint.prototype.vectorAngle = function() { var offset = 0.0; if (this.fX == 0.0) { if (this.fY == 0.0) return 0.0; if (this.fY > 0.0) return Math.PI / 2.0; else return 3 * (Math.PI/2); } else { if ((this.fX > 0.0) && (this.fY < 0.0)) offset = 2*Math.PI; else if (this.fX < 0.0) offset = Math.PI; return Math.atan( this.fY / this.fX ) + offset; } } // Rotate point around angle (in radians) and return the rotated point. TPoint.prototype.rotate = function( angle ) { var ca = Math.cos(angle); var sa = Math.sin(angle); var nx = this.fX * ca - this.fY * sa; var ny = this.fY * ca + this.fX * sa; return new TPoint( nx, ny ); } // // Test to see if the point is in the quadrilateral (or any convex polygon) // defined by four points by checking to see if it's on the same side of // the four edges. Assumes points are in clockwise (or counterclockwise) // order. This is from the classic "10 Algorithms" paper by Sutherland, // et al. // TPoint.prototype.pointInQuad = function( pts ) { var i = 0, j; var inside = true; var whichSide; while ((i < pts.length) && inside) { j = (i == pts.length-1) ? 0 : i + 1; var A = (this.fX - pts[j].fX) * (pts[i].fY - pts[j].fY); var B = (this.fY - pts[j].fY) * (pts[i].fX - pts[j].fX); if (i == 0) whichSide = (A > B); else inside = (whichSide == (A > B)); i++; } return inside; } // Report what "side" of the line p0, p1 the point is on. TPoint.prototype.sideOf = function( p0, p1 ) { var dist = this.distanceToLine( p0, p1 ); if (dist == 0.0) return 0; if (dist < 0.0) return -1; return 1; } // // Compute the distance from "this" to the line defined by p0, p1 // TPoint.prototype.distanceToLine = function( p0, p1 ) { var fBase = p0; var fDirection = p1 - p0; var other_base = this; var other_dir = new TPoint( -fDirection.fY, fDirection.fX ); // Find point where line perpendicular to p0p1 through this intersects p0p1 var online = TPoint.lineSegmentIntersect( fBase, fDirection + fBase, other_base, other_dir + other_base, false ); var fromDir = this - online; var dist = fromDir.vectorLength(); // Determine the sign by comparing the vector signs between the // point and the line and the line's perpendicular vector. if (Math.abs( fromDir.fX ) > Math.abs( fromDir.fY )) return (fromDir.fX * fDirection.fY <= 0.0) ? dist : -dist; else return (fromDir.fY * fDirection.fX > 0.0) ? dist : -dist; }; // Find where (parametrically) test falls on the // segment p10 p11. If it's beyond 0..1, there's no hit TPoint.prototype.pointInSeg = function( p0, p1 ) { // Find two vectors, p0p1 & p0test var v0 = p1 - p0; var vm = this - p0; // Ratio of dot prodcuts gives the projection of vm onto v0 var t, denom = v0.fX * v0.fX + v0.fY * v0.fY; if (denom == 0.0) // First line seg zero length... return (this == p0); else t = (v0.fX * vm.fX + v0.fY * vm.fY ) / denom; return ((t >= 0.0) && (t <= 1.0)); } // Class static methods // Find where line segment (p10 p11) intersects (p20 p21). // Returns [INF, INF] if no intersection (lines parallel) TPoint.lineSegmentIntersect = function( p10, p11, p20, p21, checkEndpoints ) { var A1, B1, C1, A2, B2, C2; // Line equations var tmp; var cross = new TPoint( Infinity, Infinity ); if (typeof(checkEndpoints) == "undefined") checkEndpoints = true; // Generate the line equations (Ax+By=C) of the // vectors Perpendicular to center->endpoint lines. A1 = p10.fY - p11.fY; B1 = p11.fX - p10.fX; C1 = A1 * p10.fX + B1 * p10.fY; A2 = p20.fY - p21.fY; B2 = p21.fX - p20.fX; C2 = A2 * p20.fX + B2 * p20.fY; // Find the intersection of above two lines by solving: // [A1 B1] [x] = [C1] // [A2 B2] [y] = [C2] tmp = A1 * B2 - B1 * A2; if (tmp == 0.0) // Parallel return cross; else { cross.fX = (B2 * C1 - B1 * C2) / tmp; cross.fY = (A1 * C2 - A2 * C1) / tmp; } if (!checkEndpoints || (cross.pointInSeg( p10, p11 ) && cross.pointInSeg( p20, p21 ))) return cross; else return new TPoint( Infinity, Infinity ); } // Clip the line segment defined by p0, p1 to rect (note the segment may be extended // as well). Returns false of the line misses the rect TPoint.clipLineToRect = function( rect, p0, p1 ) { var fBase = p0; var fDirection = p1 - p0; var leftPt, topPt, rightPt, bottomPt; var inPts = new Array(); var incount = 0; function evaluateAtX( x ) { if (fDirection.fX == 0.0) // no Y intercept... { if (x == fBase.fX) return new TPoint( x, Infinity ); else return new TPoint( Infinity, Infinity ); } else return new TPoint( x, (fDirection.fY / fDirection.fX) * (x - fBase.fX) + fBase.fY ); } function evaluateAtY( y ) { if (fDirection.fY == 0.0) // no X intercept... { if (y == fBase.fY) return new TPoint( Infinity, y ); else return new TPoint( Infinity, Infinity ); } else return new TPoint( (fDirection.fX / fDirection.fY) * (y - fBase.fY) + fBase.fX, y ); } // If line has no direction, just use the base point if (fDirection == TPoint.kOrigin) { if (rect.contains( fBase )) { p0 = fBase; p1 = fBase; return true; } else return true; // "Line" is outside of rectangle } // Find the intercepts of the rectangle on the line leftPt = evaluateAtX( rect.fLeft ); if ((leftPt.fY >= rect.fTop) && (leftPt.fY <= rect.fBottom)) inPts[incount++] = leftPt; topPt = evaluateAtY( rect.fTop ); if ((topPt.fX >= rect.fLeft) && (topPt.fX <= rect.fRight)) inPts[incount++] = topPt; rightPt = evaluateAtX( rect.fRight ); if ((rightPt.fY >= rect.fTop) && (rightPt.fY <= rect.fBottom)) inPts[incount++] = rightPt; bottomPt = evaluateAtY( rect.fBottom ); if ((bottomPt.fX >= rect.fLeft) && (bottomPt.fX <= rect.fRight)) inPts[incount++] = bottomPt; if (incount == 0) return false; // Non intersection leaves line unmodified $.bp( incount == 1); // This would be weird... p0 = inPts[0]; if (incount == 2) // Normal case p1 = inPts[1]; else // If line hits a corner, find two unique points { var i; for (i = 1; (i < incount) && (inPts[i] == p0); i++) {}; $.bp( i >= incount ); p1 = inPts[i]; } return true; } // Find the area of a convex polygon. Always returns // positive value, so the order (counter/clockwise) doesn't matter. // From Mathworld: http://mathworld.wolfram.com/PolygonArea.html TPoint.polygonArea = function( pts ) { var i, sum = 0; var n = pts.length-1; for (i = 0; i < n; ++i) sum += pts[i].fX*pts[i+1].fY - pts[i+1].fX * pts[i].fY; sum += pts[n].fX*pts[0].fY - pts[0].fX * pts[n].fY; return Math.abs(sum) * 0.5; } // // =================================== TRect =================================== // function TRect( leftTop, rightBottom ) { // Four arguments are assumed to be left top right bottom if (arguments.length == 4) { this.fLeft = arguments[0]; this.fTop = arguments[1]; this.fRight = arguments[2]; this.fBottom = arguments[3]; } else // One argument is assumed to be a list of points; the bounds is returned if ((arguments.length == 1)) { this.setToBoundsOf( arguments[0] ); } else { this.fLeft = leftTop.fX; this.fTop = leftTop.fY; this.fRight = rightBottom.fX; this.fBottom = rightBottom.fY; } } // TRect constants const kZeroRectData = new TRect( 0, 0, 0, 0 ); TRect.kZeroRect = kZeroRectData; const kInfiniteRectdata = new TRect( -Infinity, -Infinity, Infinity, Infinity ); TRect.kInfiniteRect = kInfiniteRectdata; const kRectClassname = "TRect" TRect.prototype.className = kRectClassname; // Data access TRect.prototype.getTopLeft = function() { return new TPoint( this.fLeft, this.fTop ); } TRect.prototype.getBottomRight = function() { return new TPoint( this.fRight, this.fBottom ); } TRect.prototype.getBottomLeft = function() { return new TPoint(this.fLeft, this.fBottom); } TRect.prototype.getTopRight = function() { return new TPoint(this.fRight, this.fTop); } // Return the four corner points of the rectangle as an array of points TRect.prototype.getCornerPoints = function() { return [this.getTopLeft(), this.getTopRight(), this.getBottomRight(), this.getBottomLeft()]; } // Returns true if (fRight > fLeft) && (fBottom > fTop) TRect.prototype.isEmpty = function() { return (this.fLeft >= this.fRight) || (this.fTop >= this.fBottom); } // Returns true if no coordinates are infinite and left/right and top/bottom aren't crossed. TRect.prototype.isValid = function() { if ((this.fLeft == Infinity) || (this.fRight == Infinity) || (this.fTop == Infinity) || (this.fBottom == Infinity)) return false; return (this.fLeft <= this.fRight) && (this.fTop <= this.fBottom); } TRect.prototype.getSize = function() { return new TPoint( this.fRight-this.fLeft, this.fBottom-this.fTop ); } TRect.prototype.getWidth = function() { return this.fRight - this.fLeft; } TRect.prototype.getHeight = function() { return this.fBottom-this.fTop; } TRect.prototype.getArea = function() { return this.getWidth() * this.getHeight(); } TRect.prototype.contains = function( geometry ) { if (className( geometry ) == "TPoint") // Check for point { var p = geometry; return ((p.fX >= this.fLeft) && (p.fX < this.fRight) && (p.fY >= this.fTop) && (p.fY < this.fBottom)); } else { var r = geometry; // Assume rect instead return ((r.fLeft >= this.fLeft) && (r.fRight <= this.fRight) && (r.fTop >= this.fTop) && (r.fBottom <= this.fBottom)); } } // Modifiers TRect.prototype.setToPoint = function( point ) { this.fLeft = point.fX; this.fTop = point.fY; this.fRight = point.fX;; this.fBottom = point.fY; } TRect.prototype.setTopLeft = function( point ) { this.fLeft = point.fX; this.fTop = point.fY; } TRect.prototype.setTopRight = function( point ) { this.fRight = point.fX; this.fTop = point.fY; } TRect.prototype.setBottomRight = function( point ) { this.fRight = point.fX;; this.fBottom = point.fY; } TRect.prototype.setBottomLeft = function( point ) { this.fLeft = point.fX;; this.fBottom = point.fY; } TRect.prototype.setSize = function( size ) { this.fRight = this.fLeft + size.fX; this.fBottom = this.fTop + size.fY; } TRect.prototype.setCenter = function( center ) { var delta = center - this.getCenter(); this.fLeft += delta.fX; this.fRight += delta.fX; this.fTop += delta.fY; this.fBottom += delta.fY; } TRect.prototype.setToBoundsOf = function( pointList ) { this.fLeft = pointList[0].fX; this.fTop = pointList[0].fY; this.fRight = this.fLeft; this.fBottom = this.fTop; var i; for (i = 1; i < pointList.length; i++) this.extendTo( pointList[i] ); } // Constructor version of above TRect.getBounds = function( pointList ) { var bounds = new TRect(pointList[0].fX, pointList[0].fY); bounds.setToBoundsOf( pointList ); return bounds; } TRect.prototype.extendTo = function( geometry ) { // Check incoming geometry for type... if (className(geometry) == "TPoint") { var p = geometry; if (p.fX < this.fLeft) this.fLeft = p.fX; if (p.fY < this.fTop) this.fTop = p.fY; if (p.fX > this.fRight) this.fRight = p.fX; if (p.fY > this.fBottom) this.fBottom = p.fY; } else // Not point, assume rectangle { var r = geometry; if (this.isEmpty()) { this.fTop = r.fTop; this.fLeft = r.fLeft; this.fRight = r.fRight; this.fBottom = r.fBottom; } else { if (! r.isEmpty()) { if (r.fLeft < this.fLeft) this.fLeft = r.fLeft; if (r.fTop < this.fTop) this.fTop = r.fTop; if (r.fRight > this.fRight) this.fRight = r.fRight; if (r.fBottom > this.fBottom) this.fBottom = r.fBottom; } } } } TRect.prototype.intersectWith = function( r ) { if (this.isEmpty() || r.isEmpty()) { this.fLeft = this.fRight; this.fTop = this.fBottom; } else { if (r.fLeft > this.fLeft) this.fLeft = r.fLeft; if (r.fTop > this.fTop) this.fTop = r.fTop; if (r.fRight < this.fRight) this.fRight = r.fRight; if (r.fBottom < this.fBottom) this.fBottom = r.fBottom; // If the two don't overlap, avoid an invalid rect if (this.fRight < this.fLeft) this.fLeft = this.fRight; if (this.fBottom < this.fTop) this.fTop = this.fBottom; } } // "static" class member. Returns a new, distinct rectangle. TRect.intersection = function( a, b ) { var x = new TRect( a.fLeft, a.fTop, a.fRight, a.fBottom ); x.intersectWith( b ); return x; } TRect.prototype.intersects = function( r ) { var result; if (this.isEmpty()) result = false; else if (r.isEmpty()) result = false; else result = ( (this.fLeft < r.fRight) && (this.fRight > r.fLeft) && (this.fTop < r.fBottom) && (this.fBottom > r.fTop) ); return result; } TRect.prototype.getCenter = function() { return new TPoint( (this.fLeft + this.fRight) * 0.5, (this.fTop + this.fBottom) * 0.5 ); } TRect.prototype.equalSize = function( r ) { return ( ((this.fRight - this.fLeft) == (r.fRight - r.fLeft)) && ((this.fBottom - this.fTop) == (r.fBottom - r.fTop)) ); } TRect.prototype.offset = function( dg ) { this.fLeft += dg.fX; this.fTop += dg.fY; this.fRight += dg.fX; this.fBottom += dg.fY; } TRect.prototype.inset = function( inset ) { this.fLeft += inset.fX; this.fTop += inset.fY; this.fRight -= inset.fX; this.fBottom -= inset.fY; } TRect.prototype.pin = function( pt ) { if (this.fLeft > this.fRight) pt.fX = (this.fLeft + this.fRight) / 2; else if (pt.fX < this.fLeft) pt.fX = this.fLeft; else if (pt.fX > this.fRight) pt.fX = this.fRight; if (this.fTop > this.fBottom) pt.fY = (this.fTop + this.fBottom) / 2; else if (pt.fY < this.fTop) pt.fY = this.fTop; else if (pt.fY > this.fBottom) pt.fY = this.fBottom; } // Takes the r's size and position relative to the object's rectangle and // maps it to the same size and position (proportionally) to the dest // rectangle. If the source rectangle has a height or width of zero // the rectangle r will have a corresponding infinite height or width. TRect.prototype.mapRect = function( r, dest ) { var victLT = new TPoint( r.fLeft, r.fTop ); var victRB = new TPoint( r.fRight, r.fBottom ); var srcLT = new TPoint( this.fLeft, this.fTop ); var srcRB = new TPoint( this.fRight, this.fBottom ); var destLT = new TPoint( dest.fLeft, dest.fTop ); var destRB = new TPoint( dest.fRight, dest.fBottom ); victLT -= srcLT; victRB -= srcLT; // bk bug fix - we need to use floating, rather than integer, arithmetic for scaling to work correctly. // TPoint scale = (destRB - destLT) / (srcRB - srcLT); // victLT *= scale; // victRB *= scale; var x_scale = (destRB.fX - destLT.fX) / (srcRB.fX - srcLT.fX); var y_scale = (destRB.fY - destLT.fY) / (srcRB.fY - srcLT.fY); victLT.fX = (victLT.fX * x_scale); victLT.fY = (victLT.fY * y_scale); victRB.fX = (victRB.fX * x_scale); victRB.fY = (victRB.fY * y_scale); victLT += destLT; victRB += destLT; return new TRect( victLT, victRB ); } TRect.prototype.mapPoint = function( pt, dest ) { var srcLT = new TPoint( this.fLeft, this.fTop ); var srcRB = new TPoint( this.fRight, this.fBottom ); var destLT = new TPoint( dest.fLeft, dest.fTop ); var destRB = new TPoint( dest.fRight, dest.fBottom ); pt -= srcLT; var x_scale = (destRB.fX - destLT.fX) / (srcRB.fX - srcLT.fX); var y_scale = (destRB.fY - destLT.fY) / (srcRB.fY - srcLT.fY); pt.fX = (pt.fX * x_scale); pt.fY = (pt.fY * y_scale); pt += destLT; return pt; } TRect.prototype.orderPoints = function() { if (this.fLeft > this.fRight) { var tmp = this.fRight; this.fRight = this.fLeft; this.fLeft = tmp; } if (this.fTop > this.fBottom) { var tmp = this.fTop; this.fTop = this.fBottom; this.fBottom = tmp; } } TRect.prototype["=="] = function( src ) { if (this.fLeft != src.fLeft) return false; if (this.fTop != src.fTop) return false; if (this.fRight != src.fRight) return false; if (this.fBottom != src.fBottom) return false; return true; } TRect.prototype.toString = function() { return "[" + this.fLeft.toString() + "," + this.fTop.toString() + "," + this.fRight.toString() + "," + this.fBottom.toString() + "]"; } // // Rectangle to rectangle clip, specialized for the // crop case of removing the image rect from the crop rect, // leaving behind the portions to be filled. // // OK, imagine 'minus' is completely inside of 'this'. That // leaves eight possible leftover rectangle pieces; fewer // if minus is not completely contained. We find these rects, // (called "tiles" here) and coalesce them together as possible. // For example, if minus is taller (both above & below) thisRect, // then only rects 0,3,5 and 2,4,7 are left over. // // +---+---------+---+ // | 0 | 1 | 2 | // +---+---------+---+ // | 3 | minus | 4 | // +---+---------+---+ // | 5 | 6 | 7 | // +---+---------+---+ // // AdobePatentID="P5923-US" TRect.prototype.subtract = function( minus ) { if (minus.contains( this ) || !minus.intersects( this )) return null; var self = this; var topLT = this.fTop < minus.fTop; var leftLT = this.fLeft < minus.fLeft; var rightGT = this.fRight > minus.fRight; var bottomGT = this.fBottom > minus.fBottom; // Figure out which tiles are visible var tileState = [leftLT && topLT, topLT, rightGT && topLT, // 0, 1, 2 leftLT, rightGT, // 3 4 leftLT && bottomGT, bottomGT, rightGT && bottomGT]; // 5, 6, 7 var tileUsed = [false, false, false, false, false, false, false, false]; var resultRects = []; // If a corner tile next to a side tile is visible, expand // tileRect to include it. function extendRect( tileRect, tileNum, side ) { if (tileState[tileNum] && !tileUsed[tileNum]) { tileUsed[tileNum] = true; tileRect[side] = self[side]; } } // Check each of the "side" tiles, (1, 3, 4, 6). For each one, // see if the adjacent tile is also included, and has not been // used yet. If so, expand the returned rect for that side tile // to include the neighbor. function makeAndExpandSide( sideNum, thisSide, thisDst, c1Num, c1Side, c2Num, c2Side ) { if (tileState[sideNum]) { tileUsed[sideNum] = true; var newTile = new TRect( minus.fLeft, minus.fTop, minus.fRight, minus.fBottom ); newTile[thisSide] = self[thisSide]; newTile[thisDst] = minus[thisSide]; // If either the c1 or c2 side isn't included, then // shrink the new tile to this's corresponding side if (! tileState[c1Num]) newTile[c1Side] = self[c1Side]; if (! tileState[c2Num]) newTile[c2Side] = self[c2Side]; extendRect( newTile, c1Num, c1Side ); extendRect( newTile, c2Num, c2Side ); resultRects.push( newTile ); } } // Walk around the edges, collecting and coalescing all the tiles we find. makeAndExpandSide( 1, "fTop", "fBottom", 0, "fLeft", 2, "fRight" ); makeAndExpandSide( 3, "fLeft", "fRight", 0, "fTop", 5, "fBottom" ); makeAndExpandSide( 4, "fRight", "fLeft", 2, "fTop", 7, "fBottom" ); makeAndExpandSide( 6, "fBottom", "fTop", 5, "fLeft", 7, "fRight" ); return resultRects; }; // DEBUG //aa = new TPoint(2,3); //bb = new TPoint(7,7); //r = new TRect(0,0,2,2); //t0 = new TPoint(2,7); //t1 = new TPoint(6,3); //p0 = new TPoint(2,3); //p1 = new TPoint(6,7);