// ******* Region MANAGER ******** // $axure.internal(function($ax) { var _geometry = $ax.geometry = {}; var regionMap = {}; var regionList = []; var _unregister = function(label) { var regionIndex = regionList.indexOf(label); if(regionIndex != -1) { var end = $ax.splice(regionList, regionIndex + 1); $ax.splice(regionList, regionIndex, regionList.length - regionIndex); regionList = regionList.concat(end); } delete regionMap[label]; }; _geometry.unregister = _unregister; var clear = function() { regionMap = {}; regionList = []; }; var _polygonRegistered = function(label) { return Boolean(regionMap[label]); }; _geometry.polygonRegistered = _polygonRegistered; // Must be counterclockwise, or enter/exit will be wrong var _registerPolygon = function(label, points, callback, info) { var regionIndex = regionList.indexOf(label); if(regionIndex == -1) regionList.push(label); regionMap[label] = { points: points, callback: callback, info: info }; }; _geometry.registerPolygon = _registerPolygon; var _getPolygonInfo = function(label) { if(!_polygonRegistered(label)) return undefined; return regionMap[label].info; }; _geometry.getPolygonInfo = _getPolygonInfo; var _genRect = function(info, roundHalfPixel) { var x = info.pagex(); var y = info.pagey(); var w = info.width(); var h = info.height(); if(roundHalfPixel) { if(x % 1 != 0) { x = Math.floor(x); w++; } if(y % 1 != 0) { y = Math.floor(y); h++; } } var r = x + w; var b = y + h; var rect = { X: function() { return x; }, Y: function() { return y; }, Wigth: function() { return w; }, Height: function() { return h; }, Left: function() { return x; }, Right: function() { return r; }, Top: function() { return y; }, Bottom: function() { return b; } }; return rect; }; _geometry.genRect = _genRect; var _genPoint = function(x, y) { return { x: x, y: y }; }; _geometry.genPoint = _genPoint; var oldPoint = _genPoint(0, 0); _geometry.tick = function(x, y, end) { var lastPoint = oldPoint; var nextPoint = oldPoint = _genPoint(x, y); var line = { p1: lastPoint, p2: nextPoint }; if(!regionList.length) return; for(var i = 0; i < regionList.length; i++) { var region = regionMap[regionList[i]]; var points = region.points; for(var j = 0; j < points.length; j++) { var startSegment = points[j]; var endSegment = points[(j + 1) % points.length]; var intersectInfo = linesIntersect(line, { p1: startSegment, p2: endSegment }); if(intersectInfo) { region.callback(intersectInfo); break; } } } if(end) clear(); }; // Info if the one line touches the other (even barely), false otherwise // Info includes point, if l1 is entering or exiting l2, and any ties that happened, or parallel info var linesIntersect = function(l1, l2) { var retval = {}; var ties = {}; var l1p1 = l1.p1.x < l1.p2.x || (l1.p1.x == l1.p2.x && l1.p1.y < l1.p2.y) ? l1.p1 : l1.p2; var l1p2 = l1.p1.x < l1.p2.x || (l1.p1.x == l1.p2.x && l1.p1.y < l1.p2.y) ? l1.p2 : l1.p1; var m1 = (l1p2.y - l1p1.y) / (l1p2.x - l1p1.x); var l2p1 = l2.p1.x < l2.p2.x || (l2.p1.x == l2.p2.x && l2.p1.y < l2.p2.y) ? l2.p1 : l2.p2; var l2p2 = l2.p1.x < l2.p2.x || (l2.p1.x == l2.p2.x && l2.p1.y < l2.p2.y) ? l2.p2 : l2.p1; var m2 = (l2p2.y - l2p1.y) / (l2p2.x - l2p1.x); var l1Vert = l1.p1.x == l1.p2.x; var l2Vert = l2.p1.x == l2.p2.x; if(l1Vert || l2Vert) { if(l1Vert && l2Vert) { // If the lines don't follow the same path, return if(l1p1.x != l2p1.x) return false; // if they never meet, return if(l1p2.y < l2p1.y || l1p1.y > l2p2.y) return false; var firstVert = l1p1.y >= l2p1.y ? l1p1 : l2p1; var secondVert = l1p2.y <= l2p2.y ? l1p2 : l2p2; // First is from the perspective of l1 retval.parallel = { first: l1p1 == l1.p1 ? firstVert : secondVert, second: l1p2 == l1.p2 ? secondVert : firstVert, sameDirection: (l1p1 == l1.p1) == (l2p1 == l2.p1) }; return retval; } var x1 = l2Vert ? l1p1.x : l2p1.x; var x2 = l2Vert ? l1p2.x : l2p2.x; var xVert = l2Vert ? l2p1.x : l1p1.x; var y = l2Vert ? l1p1.y + (xVert - x1) * m1 : l2p1.y + (xVert - x1) * m2; var y1 = l2Vert ? l2p1.y : l1p1.y; var y2 = l2Vert ? l2p2.y : l1p2.y; if(xVert >= x1 && xVert <= x2 && y >= y1 && y <= y2) { retval.point = { x: xVert, y: y }; retval.exiting = l2Vert == (y1 == (l2Vert ? l2.p1.y : l1.p1.y)) == (x1 == (l2Vert ? l1.p1.x : l2.p1.x)); retval.entering = !retval.exiting; // Calculate ties if(x1 == xVert) { ties[l2Vert ? 'l1' : 'l2'] = (x1 == (l2Vert ? l1.p1.x : l2.p1.x)) ? 'start' : 'end'; retval.ties = ties; } else if(x2 == xVert) { ties[l2Vert ? 'l1' : 'l2'] = (x2 == (l2Vert ? l1.p2.x : l2.p2.x)) ? 'end' : 'start'; retval.ties = ties; } if(y1 == y) { ties[l2Vert ? 'l2' : 'l1'] = (y1 == (l2Vert ? l2.p1.y : l1.p1.y)) ? 'start' : 'end'; retval.ties = ties; } else if(y2 == y) { ties[l2Vert ? 'l2' : 'l1'] = (y2 == (l2Vert ? l2.p2.y : l1.p2.y)) ? 'end' : 'start'; retval.ties = ties; } return retval; } return false; } // If here, no vertical lines if(m1 == m2) { // If the lines don't follow the same path, return if(l1p1.y != (l2p1.y + (l1p1.x - l2p1.x) * m1)) return false; // if they never meet, return if(l1p2.x < l2p1.x || l1p1.x > l2p2.x) return false; var first = l1p1.x >= l2p1.x ? l1p1 : l2p1; var second = l1p2.x <= l2p2.x ? l1p2 : l2p2; // First is from the perspective of l1 retval.parallel = { first: l1p1 == l1.p1 ? first : second, second: l1p2 == l1.p2 ? second : first, sameDirection: (l1p1 == l1.p1) == (l2p1 == l2.p1) }; return retval; } var x = (l2p1.y - l2p1.x * m2 - l1p1.y + l1p1.x * m1) / (m1 - m2); // Check if x is out of bounds if(x >= l1p1.x && x <= l1p2.x && x >= l2p1.x && x <= l2p2.x) { var y = l1p1.y + (x - l1p1.x) * m1; retval.point = { x: x, y: y }; retval.entering = m1 > m2 == (l1p1 == l1.p1) == (l2p1 == l2.p1); retval.exiting = !retval.entering; // Calculate ties if(l1.p1.x == x) { ties.l1 = 'start'; retval.ties = ties; } else if(l1.p2.x == x) { ties.l1 = 'end'; retval.ties = ties; } if(l2.p1.x == x) { ties.l2 = 'start'; retval.ties = ties; } else if(l2.p2.x == x) { ties.l2 = 'end'; retval.ties = ties; } return retval; } return false; }; var _checkInsideRegion = function(label, point) { if(!_polygonRegistered(label)) return false; return _checkInside(regionMap[label].points, point || $ax.mouseLocation); }; _geometry.checkInsideRegion = _checkInsideRegion; // Returns true if point is inside the polygon, including ties var _checkInside = function(polygon, point) { // Make horizontal line wider than the polygon, with the y of point to test location var firstX = polygon[0].x; var secondX = firstX; var i; for(i = 1; i < polygon.length; i++) { var polyX = polygon[i].x; firstX = Math.min(firstX, polyX); secondX = Math.max(secondX, polyX); } var line = { p1: _genPoint(--firstX, point.y), p2: _genPoint(++secondX, point.y) }; // If entered true, with closest intersection says you are inside the polygon. var entered = false; // Closest is the closest intersection to the left of the point var closest = line.p1.x; // This is for if intersections hit the same point, to find out which is correct var cos = -2; var getCos = function(line) { var x = line.p2.x - line.p1.x; var y = line.p2.y - line.p1.y; return x / Math.sqrt(x * x + y * y); }; for(i = 0; i < polygon.length; i++) { var polyLine = { p1: polygon[i], p2: polygon[(i + 1) % polygon.length] }; var intersectInfo = linesIntersect(line, polyLine); if(!intersectInfo) continue; if(intersectInfo.parallel) { // Only really care about this if it actually touches the point if(intersectInfo.parallel.first.x <= point.x && intersectInfo.parallel.second.x >= point.x) return true; continue; } var intersectionX = intersectInfo.point.x; if(intersectionX > point.x || intersectionX < closest) continue; if(intersectionX == point.x) return true; // If closer than last time, reset cosine. if(intersectionX != closest) cos = -2; // For getting cosine, need to possibly reverse the direction of polyLine. if(intersectInfo.ties) { // Tie must be on l2, if the ties is end, reverse so cosine indicates how close the angle is to that of 'point' from here. if(intersectInfo.ties.l2 == 'end') polyLine = { p1: polyLine.p2, p2: polyLine.p1 }; } else { // It is on both side, so you can take the larger one if(polyLine.p1.x > polyLine.p2.x) polyLine = { p1: polyLine.p2, p2: polyLine.p1 }; } var currCos = getCos(polyLine); if(currCos > cos) { cos = currCos; closest = intersectionX; entered = intersectInfo.entering; } } return entered; }; _geometry.checkInside = _checkInside; });