All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.visibility.VisibilityPolygon Maven / Gradle / Ivy

There is a newer version: 2.5.0
Show newest version
/**
 Ported by David Turner from Visilibity, by Karl J. Obermeyer
   
 
 This port undoubtedly introduced a number of bugs (and removed some features).
 
 Bug reports should be directed to the OpenTripPlanner project, unless they 
 can be reproduced in the original VisiLibity.
  
 This program is free software: you can redistribute it and/or modify it under
 the terms of the GNU Lesser General Public License as published by the
 Free Software Foundation, either version 3 of the License, or (at your
 option) any later version.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this program.  If not, see .
 */

package org.opentripplanner.visibility;

import java.util.PriorityQueue;
import java.util.HashSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class VisibilityPolygon extends VLPolygon {
    private static Logger log = LoggerFactory.getLogger(VisibilityPolygon.class);
    VLPoint observer;

    public boolean is_spike(VLPoint observer, VLPoint point1, VLPoint point2, VLPoint point3, double epsilon) {

        return (
        // Make sure observer not colocated with any of the points.
        observer.distance(point1) > epsilon && observer.distance(point2) > epsilon
                && observer.distance(point3) > epsilon
                // Test whether there is a spike with point2 as the tip
                && ((observer.distance(point2) >= observer.distance(point1) && observer
                        .distance(point2) >= observer.distance(point3)) || (observer
                        .distance(point2) <= observer.distance(point1) && observer.distance(point2) <= observer
                        .distance(point3)))
        // && the pike is sufficiently sharp,
        && Math.max(point2.distance(new Ray(observer, point1)),
                point2.distance(new Ray(observer, point3))) <= epsilon);
        // Formerly used
        // Math.abs( Polygon(point1, point2, point3).area() ) < epsilon
    }

    public void chop_spikes_at_back(VLPoint observer, double epsilon) {
        // Eliminate "special case" vertices of the visibility polygon.
        // While the top three vertices form a spike.
        while (vertices.size() >= 3
                && is_spike(observer, vertices.get(vertices.size() - 3),
                        vertices.get(vertices.size() - 2), vertices.get(vertices.size() - 1),
                        epsilon)) {
            vertices.set(vertices.size() - 2, vertices.get(vertices.size() - 1));
            vertices.remove(vertices.size() - 1);
        }
    }

    void chop_spikes_at_wrap_around(VLPoint observer, double epsilon) {
        // Eliminate "special case" vertices of the visibility polygon at
        // wrap-around. While the there's a spike at the wrap-around,
        while (vertices.size() >= 3
                && is_spike(observer, vertices.get(vertices.size() - 2),
                        vertices.get(vertices.size() - 1), vertices.get(0), epsilon)) {
            // Chop off the tip of the spike.
            vertices.remove(vertices.size() - 1);
        }
    }

    void chop_spikes(VLPoint observer, double epsilon) {
        HashSet spike_tips = new HashSet();
        ArrayList vertices_temp = new ArrayList();
        // Middle point is potentially the tip of a spike
        for (int i = 0; i < vertices.size(); i++)
            if (get(i + 2).distance(new LineSegment(get(i), get(i + 1))) <= epsilon
                    || get(i).distance(new LineSegment(get(i + 1), get(i + 2))) <= epsilon)
                spike_tips.add(get(i + 1));

        for (int i = 0; i < vertices.size(); i++)
            if (!spike_tips.contains(vertices.get(i)))
                vertices_temp.add(vertices.get(i));
        vertices = vertices_temp;
    }

    public VisibilityPolygon(VLPoint observer, Environment environment_temp, double epsilon) {
        this.observer = observer;
        // Visibility polygon algorithm for environments with holes
        // Radial line (AKA angular plane) sweep technique.
        //
        // Based on algorithms described in
        //
        // [1] "Automated Camera Layout to Satisfy Task-Specific and
        // Floorplan-Specific Coverage Requirements" by Ugur Murat Erdem
        // && Stan Scarloff, April 15, 2004
        // available at BUCS Technical Report Archive:
        // http://www.cs.bu.edu/techreports/pdf/2004-015-camera-layout.pdf
        //
        // [2] "Art Gallery Theorems && Algorithms" by Joseph O'Rourke
        //
        // [3] "Visibility Algorithms in the Plane" by Ghosh
        //

        // We define a k-point is a point seen on the other side of a
        // visibility occluding corner. This name is appropriate because
        // the vertical line in the letter "k" is like a line-of-sight past
        // the corner of the "k".

        //
        // Preconditions:
        // (1) the Environment is epsilon-valid,
        // (2) the Point observer is actually in the Environment
        // environment_temp,
        // (3) the guard has been epsilon-snapped to the boundary, followed
        // by vertices of the environment (the order of the snapping
        // is important).
        //
        // :WARNING:
        // For efficiency, the assertions corresponding to these
        // preconditions have been excluded.
        //
        assert (environment_temp.is_valid(epsilon));
        assert (environment_temp.is_in_standard_form());
        assert (observer.in(environment_temp, epsilon));

        // true => data printed to terminal
        // false => silent

        // The visibility polygon cannot have more vertices than the environment.
        vertices.ensureCapacity(environment_temp.n());

        //
        // --------PREPROCESSING--------
        //

        // construct a POLAR EDGE LIST from environment_temp's outer
        // boundary and holes. During this construction, those edges are
        // split which either (1) cross the ray emanating from the observer
        // parallel to the x-axis (of world coords), or (2) contain the
        // observer in their relative interior (w/in epsilon). Also, edges
        // having first vertex bearing >= second vertex bearing are
        // eliminated because they cannot possibly contribute to the
        // visibility polygon.

        final Angle ANGLE_PI = new Angle(Math.PI);
        final Angle ANGLE_ZERO = new Angle(0.0);
        ArrayList elp = new ArrayList();
        PolarPoint ppoint1, ppoint2;
        PolarPoint split_bottom, split_top;
        double t;
        // If the observer is standing on the Enviroment boundary with its
        // back to the wall, these will be the bearings of the next vertex
        // to the right && to the left, respectively.
        Angle right_wall_bearing = new Angle(0.0);
        Angle left_wall_bearing = new Angle(0.0);
        for (int i = 0; i <= environment_temp.h(); i++) {
            VLPolygon polygon = environment_temp.get(i);

            for (int j = 0; j < polygon.n(); j++) {
                ppoint1 = new PolarPoint(observer, polygon.get(j));
                ppoint2 = new PolarPoint(observer, polygon.get(j + 1));
                log.debug("contemplating " +  ppoint1 + " and " +  ppoint1);

                // If the observer is in the relative interior of the edge.
                if (observer.in_relative_interior_of(new LineSegment(ppoint1, ppoint2), epsilon)) {
                    log.debug("in relative interior");

                    // Split the edge at the observer && add the resulting two
                    // edges to elp (the polar edge list).
                    split_bottom = new PolarPoint(observer, observer);
                    split_top = new PolarPoint(observer, observer);

                    if (ppoint2.bearing.equals(ANGLE_ZERO))
                        ppoint2.set_bearing_to_2pi();

                    left_wall_bearing = ppoint1.bearing.clone();
                    right_wall_bearing = ppoint2.bearing.clone();

                    elp.add(new PolarEdge(ppoint1, split_bottom));
                    elp.add(new PolarEdge(split_top, ppoint2));
                    continue;
                }

                // Else if the observer is on first vertex of edge.
                else if (observer.distance(ppoint1) <= epsilon) {
                    log.debug("on first vertex");

                    if (ppoint2.bearing.equals(ANGLE_ZERO)) {
                        ppoint2.set_bearing_to_2pi();
                    }
                    // Get right wall bearing.
                    right_wall_bearing = ppoint2.bearing.clone();
                    elp.add(new PolarEdge(new PolarPoint(observer, observer), ppoint2));
                    continue;
                }
                // Else if the observer is on second vertex of edge.
                else if (observer.distance(ppoint2) <= epsilon) {
                    log.debug("on second vertex");

                    // Get left wall bearing.
                    left_wall_bearing = ppoint1.bearing.clone();
                    elp.add(new PolarEdge(ppoint1, new PolarPoint(observer, observer)));
                    continue;
                }

                // Otherwise the observer is not on the edge.

                // If edge not horizontal (w/in epsilon).
                else if (Math.abs(ppoint1.y - ppoint2.y) > epsilon) {
                    log.debug("off edge");

                    // Possible source of numerical instability?
                    t = (observer.y - ppoint2.y) / (ppoint1.y - ppoint2.y);
                    // If edge crosses the ray emanating horizontal && right of
                    // the observer.
                    if (0 < t && t < 1 && observer.x < t * ppoint1.x + (1 - t) * ppoint2.x) {
                        log.debug("crosses ray");

                        // If first point is above, omit edge because it runs
                        // 'against the grain'.
                        if (ppoint1.y > observer.y)
                            continue;
                        // Otherwise split the edge, making sure angles are assigned
                        // correctly on each side of the split point.
                        split_bottom = new PolarPoint(observer, new VLPoint(t * ppoint1.x + (1 - t)
                                * ppoint2.x, observer.y));
                        split_top = new PolarPoint(observer, new VLPoint(t * ppoint1.x + (1 - t)
                                * ppoint2.x, observer.y));
                        split_top.set_bearing(ANGLE_ZERO);
                        split_bottom.set_bearing_to_2pi();
                        elp.add(new PolarEdge(ppoint1, split_bottom));
                        elp.add(new PolarEdge(split_top, ppoint2));
                        continue;
                    } else {
                        if (ppoint1.bearing.compareTo(ppoint2.bearing) >= 0
                                && ppoint2.bearing.equals(ANGLE_ZERO)
                                && ppoint1.bearing.compareTo(ANGLE_PI) > 0) {
                            ppoint2.set_bearing_to_2pi();
                        // Filter out edges which run 'against the grain'.
                        } else if ((ppoint1.bearing.equals(ANGLE_ZERO) && 
                                ppoint2.bearing.compareTo(ANGLE_PI) > 0)
                                || ppoint1.bearing.compareTo(ppoint2.bearing) >= 0) {
                            continue;
                        }
                    }
                    elp.add(new PolarEdge(ppoint1, ppoint2));
                    continue;
                }
                // If edge is horizontal (w/in epsilon).
                else {
                    log.debug("epsilon horizontal");
                    // Filter out edges which run 'against the grain'.
                    if (ppoint1.bearing.compareTo(ppoint2.bearing) >= 0)
                        continue;
                    elp.add(new PolarEdge(ppoint1, ppoint2));
                }
            }
        }

        // construct a SORTED LIST, q1, OF VERTICES represented by
        // PolarPointWithEdgeInfo objects. A
        // PolarPointWithEdgeInfo is a derived class of PolarPoint
        // which includes (1) a pointer to the corresponding edge
        // (represented as a PolarEdge) in the polar edge list elp, and
        // (2) a boolean(is_first) which is true iff that vertex is the
        // first Point of the respective edge (is_first == false => it's
        // second Point). q1 is sorted according to lex. order of polar
        // coordinates just as PolarPoints are, but with the additional
        // requirement that if two vertices have equal polar coordinates,
        // the vertex which is the first point of its respective edge is
        // considered greater. q1 will serve as an event point queue for
        // the radial sweep.
        ArrayList q1 = new ArrayList();
        PolarPointWithEdgeInfo ppoint_wei1 = new PolarPointWithEdgeInfo(), ppoint_wei2 = new PolarPointWithEdgeInfo();
        Iterator elp_iterator = elp.iterator();
        while (elp_iterator.hasNext()) {
            PolarEdge edge = elp_iterator.next();
            ppoint_wei1.set_polar_point(edge.first);
            ppoint_wei1.incident_edge = edge;
            ppoint_wei1.is_first = true;
            ppoint_wei2.set_polar_point(edge.second);
            ppoint_wei2.incident_edge = edge;
            ppoint_wei2.is_first = false;
            // If edge contains the observer, then adjust the bearing of
            // the PolarPoint containing the observer.
            if (observer.distance(ppoint_wei1) <= epsilon) {
                if (right_wall_bearing.compareTo(left_wall_bearing) > 0) {
                    ppoint_wei1.set_bearing(right_wall_bearing);
                    edge.first.set_bearing(right_wall_bearing);
                } else {
                    ppoint_wei1.set_bearing(ANGLE_ZERO);
                    edge.first.set_bearing(ANGLE_ZERO);
                }
            } else if (observer.distance(ppoint_wei2) <= epsilon) {
                if (right_wall_bearing.compareTo(left_wall_bearing) > 0) {
                    ppoint_wei2.set_bearing(right_wall_bearing);
                    edge.second.set_bearing(right_wall_bearing);
                } else {
                    ppoint_wei2.set_bearing_to_2pi();
                    edge.second.set_bearing_to_2pi();
                }
            }
            q1.add(ppoint_wei1.clone());
            q1.add(ppoint_wei2.clone());
        }

        // Put event point in correct order.
        // Collections.sort is a stable sort.
        Collections.sort(q1);
        for (PolarPointWithEdgeInfo q : q1) {
            log.debug("q: " + q);
        }
        //
        // -------PREPARE FOR MAIN LOOP-------
        //

        // current_vertex is used to hold the event point (from q1)
        // considered at iteration of the main loop.

        //
        PolarPointWithEdgeInfo current_vertex = new PolarPointWithEdgeInfo();
        // Note active_edge and e are not actually edges themselves, but
        // iterators pointing to edges. active_edge keeps track of the
        // current edge visible during the sweep. e is an auxiliary
        // variable used in calculation of k-points
        PolarEdge active_edge, e;
        // More aux vars for computing k-points.
        PolarPoint k = new PolarPoint();
        double k_range;
        LineSegment xing;

        // Priority queue of edges, where lower (first) priority indicates closer
        // range to observer along current ray (of ray sweep).
        IncidentEdgeCompare my_iec = new IncidentEdgeCompare(observer, current_vertex, epsilon);
        PriorityQueue q2 = new PriorityQueue(elp.size(), my_iec);

        // Initialize main loop.
        current_vertex.set(q1.remove(0));
        active_edge = current_vertex.incident_edge;

        // Insert e into q2 as long as it doesn't contain the
        // observer.
        if (observer.distance(active_edge.first) > epsilon
                && observer.distance(active_edge.second) > epsilon) {

            q2.add(active_edge);
        }

        vertices.add(new VLPoint(current_vertex));
        log.debug("adding: " + current_vertex + "\n--");

        // -------BEGIN MAIN LOOP-------//
        //
        // Perform radial sweep by sequentially considering each vertex
        // (event point) in q1.

        while (!q1.isEmpty()) {

            // Pop current_vertex from q1.
            current_vertex.set(q1.remove(0));
            log.debug("cv: " + current_vertex);

            // ---Handle Event Point---

            // TYPE 1: current_vertex is the _second_vertex_ of active_edge.
            if (current_vertex.incident_edge.equals(active_edge) && !current_vertex.is_first) {
                log.debug( "type 1");

                if (!q1.isEmpty()) {
                    // If the next vertex in q1 is contiguous.
                    if (current_vertex.distance(q1.get(0)) <= epsilon) {
                        continue;
                    }
                }

                // Push current_vertex onto visibility polygon
                vertices.add(new VLPoint(current_vertex));
                log.debug("adding: " + current_vertex);

                chop_spikes_at_back(observer, epsilon);

                while (!q2.isEmpty()) {
                    e = q2.peek();
                    log.debug("q2: " + e);
                    // If the current_vertex bearing has not passed, in the
                    // lex. order sense, the bearing of the second point of the
                    // edge at the front of q2.
                    if ((current_vertex.bearing.get() <= e.second.bearing.get())
                    // For robustness.
                            && new Ray(observer, current_vertex.bearing).distance(e.second) >= epsilon
                    /*
                     * was && std::min( distance(Ray(observer, current_vertex.bearing), e.second),
                     * distance(Ray(observer, e.second.bearing), current_vertex) ) >= epsilon
                     */
                    ) {
                        // Find intersection point k of ray (through
                        // current_vertex) with edge e.
                        xing = new Ray(observer, current_vertex.bearing).intersection(
                                new LineSegment(e.first, e.second), epsilon);

                        // assert( xing.size() > 0 );

                        if (xing.size() > 0) {
                            k = new PolarPoint(observer, xing.first());
                        } else { // Error contingency.
                            k = current_vertex;
                            e = current_vertex.incident_edge;
                        }

                        // Push k onto the visibility polygon.
                        vertices.add(new VLPoint(k));
                        log.debug("adding k1: " + k);
                        chop_spikes_at_back(observer, epsilon);
                        active_edge = e;
                        break;
                    }

                    q2.poll();
                }
            } // Close Type 1.

            // If current_vertex is the _first_vertex_ of its edge.
            if (current_vertex.is_first) {
                log.debug("is first");
                // Find intersection point k of ray (through current_vertex)
                // with active_edge.

                xing = new Ray(observer, current_vertex.bearing).intersection(new LineSegment(
                        active_edge.first, active_edge.second), epsilon);
                if (xing.size() == 0
                        || (active_edge.first.distance(observer) <= epsilon && active_edge.second.bearing
                                .compareTo(current_vertex.bearing) <= 0)
                        || active_edge.second.compareTo(current_vertex) < 0) {

                    k_range = Double.POSITIVE_INFINITY;
                } else {
                    k = new PolarPoint(observer, xing.first());
                    k_range = k.range;
                }

                // Incident edge of current_vertex.
                e = current_vertex.incident_edge;

                // Insert e into q2 as long as it doesn't contain the
                // observer.
                if (observer.distance(e.first) > epsilon && observer.distance(e.second) > epsilon) {

                    q2.add(e);
                }

                // TYPE 2: current_vertex is (1) a first vertex of some edge
                // other than active_edge, && (2) that edge should not become
                // the next active_edge. This happens, e.g., if that edge is
                // (rangewise) in back along the current bearing.
                if (k_range < current_vertex.range) {
                    // this is empty, in the original too -DMT
                    log.debug("type 2");
                } // Close Type 2.

                // TYPE 3: current_vertex is (1) the first vertex of some edge
                // other than active_edge, && (2) that edge should become the
                // next active_edge. This happens, e.g., if that edge is
                // (rangewise) in front along the current bearing.
                if (k_range >= current_vertex.range) {
                    // Push k onto the visibility polygon unless effectively
                    // contiguous with current_vertex.
                    log.debug("type 3");
                    if (xing.size() > 0 && k_range != Double.POSITIVE_INFINITY
                            && k.distance(current_vertex) > epsilon
                            && active_edge.first.distance(observer) > epsilon) {

                        // Push k-point onto the visibility polygon.
                        vertices.add(new VLPoint(k));
                        log.debug("adding k2: " + k);
                        chop_spikes_at_back(observer, epsilon);
                    }

                    // Push current_vertex onto the visibility polygon.
                    vertices.add(new VLPoint(current_vertex));
                    log.debug("adding: " + current_vertex);
                    chop_spikes_at_back(observer, epsilon);
                    // Set active_edge to edge of current_vertex.
                    active_edge = e;

                } // Close Type 3.
            }

        } //
          //
          // -------END MAIN LOOP-------//

        // The VisibilityPolygon should have a minimal representation
        chop_spikes_at_wrap_around(observer, epsilon);
        eliminate_redundant_vertices(epsilon);
        chop_spikes(observer, epsilon);
        enforce_standard_form();

    }

    VisibilityPolygon(VLPoint observer, VLPolygon polygon_temp, double epsilon) {
        this(observer, new Environment(polygon_temp), epsilon);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy