geometry.js 11.1 KB
// ******* 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;
            if(!region.checked) {
                if(!_checkInside(points, $ax.mouseLocation)) {
                    region.callback({ outside: true });
                    continue;
                }
                region.checked = true;
            }
            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;
});