001package jmri.jmrix.rps;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.awt.Shape;
005import java.awt.geom.GeneralPath;
006import java.util.Arrays;
007import javax.vecmath.Point3d;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * Represent a region in space for the RPS system.
013 * <p>
014 * The region is specified by a <em>right-handed</em>
015 * set of points.
016 * <p>
017 * Regions are immutable once created.
018 * <p>
019 * This initial implementation of a Region is inherently 2-dimensional,
020 * deferring use of the 3rd (Z) dimension to a later implementation. It uses a
021 * Java2D GeneralPath to handle the inside/outside calculations.
022 *
023 * @author Bob Jacobsen Copyright (C) 2007, 2008
024 */
025@javax.annotation.concurrent.Immutable
026public class Region {
027
028    public Region(Point3d[] points) {
029        super();
030
031        initPath(points);
032
033        // old init
034        if (points.length < 3) {
035            log.error("Not enough points to define region");
036        }
037        this.points = Arrays.copyOf(points, points.length);
038    }
039
040    @SuppressFBWarnings(value = "JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS", justification = "internal state, not changeable from outside")
041    GeneralPath path;
042
043    /**
044     * Ctor from a string like "(0,0,0);(1,0,0);(1,1,0);(0,1,0)" .
045     * @param s construction string.
046     */
047    public Region(String s) {
048        String[] pStrings = s.split(";");
049        points = new Point3d[pStrings.length];
050
051        // load each point
052        for (int i = 0; i < points.length; i++) {
053            // remove leading ( and trailing )
054            String coords = pStrings[i].substring(1, pStrings[i].length() - 1);
055            String[] coord = coords.split(",");
056            if (coord.length != 3) {
057                log.error("need to have three coordinates in {}", pStrings[i]);
058            }
059            double x = Double.valueOf(coord[0]);
060            double y = Double.valueOf(coord[1]);
061            double z = Double.valueOf(coord[2]);
062            points[i] = new Point3d(x, y, z);
063        }
064        initPath(points);
065    }
066
067    /**
068     * Provide Java2D access to the shape of this region.
069     * <p>
070     * This should provide a copy of the GeneralPath path, to keep the
071     * underlying object immutable, but by returning a Shape type hopefully we
072     * achieve the same result with a little better performance. Please don't
073     * assume you can cast and modify this.
074     * @return the path.
075     */
076    public Shape getPath() {
077        return path;
078    }
079
080    void initPath(Point3d[] points) {
081        if (points.length < 3) {
082            log.error("Region needs at least three points to have non-zero area");
083        }
084
085        path = new GeneralPath();
086        path.moveTo((float) points[0].x, (float) points[0].y);
087        for (int i = 1; i < points.length; i++) {
088            path.lineTo((float) points[i].x, (float) points[i].y);
089        }
090        path.lineTo((float) points[0].x, (float) points[0].y);
091    }
092
093    @Override
094    public String toString() {
095        StringBuilder retval = new StringBuilder("");
096        for (int i = 0; i < points.length; i++) {
097            retval.append(String.format("(%f,%f,%f)", points[i].x, points[i].y, points[i].z));
098            if (i != points.length - 1) {
099                retval.append(";");
100            }
101        }
102        return retval.toString();
103    }
104
105    public boolean isInside(Point3d p) {
106        return path.contains(p.x, p.y);
107    }
108
109    @Override
110    public boolean equals(Object ro) {
111        if (ro == null || !(ro instanceof Region)) {
112            return false;
113        }
114        try {
115            Region r = (Region) ro;
116            if (points.length != r.points.length) {
117                return false;
118            }
119            for (int i = 0; i < points.length; i++) {
120                if (!points[i].epsilonEquals(r.points[i], 0.001)) {
121                    return false;
122                }
123            }
124            return true;
125        } catch (Exception e) {
126            return false;
127        }
128    }
129
130    @Override
131    public int hashCode() {
132        int code = 0;
133        if (points.length >= 1) {
134            code = 10000 * (int) points[0].x + 10000 * (int) points[0].y;
135        }
136        if (points.length >= 2) {
137            code = code + 10000 * (int) points[1].x + 10000 * (int) points[1].y;
138        }
139        return code;
140    }
141
142    final Point3d[] points;
143
144    private final static Logger log = LoggerFactory.getLogger(Region.class);
145
146}