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}