001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.math.geometry.shape; 031 032import org.openimaj.math.geometry.point.Point2d; 033import org.openimaj.math.geometry.point.Point2dImpl; 034 035import Jama.Matrix; 036 037/** 038 * A circle shape 039 * 040 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 041 * 042 */ 043public class Circle implements Shape { 044 protected Point2d centre; 045 protected float radius; 046 047 /** 048 * Construct a circle with the given position and radius 049 * 050 * @param x 051 * the x-coordinate of the centre 052 * @param y 053 * the y-coordinate of the centre 054 * @param radius 055 * the radius 056 */ 057 public Circle(float x, float y, float radius) { 058 this(new Point2dImpl(x, y), radius); 059 } 060 061 /** 062 * Construct a circle with the given position and radius 063 * 064 * @param centre 065 * the coordinate of the centre 066 * @param radius 067 * the radius 068 */ 069 public Circle(Point2d centre, float radius) { 070 this.centre = centre; 071 this.radius = radius; 072 } 073 074 /** 075 * Construct a circle with the given circle 076 * 077 * @param c 078 * the circle 079 */ 080 public Circle(Circle c) { 081 this.centre = c.centre; 082 this.radius = c.radius; 083 } 084 085 @Override 086 public boolean isInside(Point2d point) { 087 final double dx = (centre.getX() - point.getX()); 088 final double dy = (centre.getY() - point.getY()); 089 090 final double dist = Math.sqrt(dx * dx + dy * dy); 091 return dist <= radius; 092 } 093 094 @Override 095 public Rectangle calculateRegularBoundingBox() { 096 final int x = Math.round(centre.getX() - radius); 097 final int y = Math.round(centre.getY() - radius); 098 final int r = Math.round(radius * 2); 099 100 return new Rectangle(x, y, r, r); 101 } 102 103 @Override 104 public void translate(float x, float y) { 105 centre.setX(centre.getX() + x); 106 centre.setY(centre.getY() + y); 107 } 108 109 @Override 110 public void scale(float sc) { 111 radius *= sc; 112 centre.setX(centre.getX() * sc); 113 centre.setY(centre.getY() * sc); 114 } 115 116 @Override 117 public void scale(Point2d centre, float sc) { 118 this.translate(-centre.getX(), -centre.getY()); 119 scale(sc); 120 this.translate(centre.getX(), centre.getY()); 121 } 122 123 @Override 124 public void scaleCentroid(float sc) { 125 radius *= sc; 126 } 127 128 @Override 129 public Point2d calculateCentroid() { 130 return centre; 131 } 132 133 @Override 134 public double calculateArea() { 135 return Math.PI * radius * radius; 136 } 137 138 @Override 139 public double minX() { 140 return Math.round(centre.getX() - radius); 141 } 142 143 @Override 144 public double minY() { 145 return Math.round(centre.getY() - radius); 146 } 147 148 @Override 149 public double maxX() { 150 return Math.round(centre.getX() + radius); 151 } 152 153 @Override 154 public double maxY() { 155 return Math.round(centre.getY() + radius); 156 } 157 158 @Override 159 public double getWidth() { 160 return Math.round(2 * radius); 161 } 162 163 @Override 164 public double getHeight() { 165 return Math.round(2 * radius); 166 } 167 168 @Override 169 public Shape transform(Matrix transform) { 170 // TODO: could handle different cases and hand 171 // back correct shape here (i.e. circle or ellipse) 172 // depending on transform 173 return asPolygon().transform(transform); 174 } 175 176 @Override 177 public Polygon asPolygon() { 178 final Polygon poly = new Polygon(); 179 final Point2dImpl[] v = new Point2dImpl[360]; 180 for (int i = 0; i < 90; i++) { 181 final double theta = Math.toRadians(i); 182 final float xx = (float) (radius * Math.cos(theta)); 183 final float yy = (float) (radius * Math.sin(theta)); 184 v[i] = new Point2dImpl(xx, yy); 185 v[i + 90] = new Point2dImpl(-yy, xx); 186 v[i + 180] = new Point2dImpl(-xx, -yy); 187 v[i + 270] = new Point2dImpl(yy, -xx); 188 } 189 190 for (int i = 0; i < 360; i++) 191 poly.points.add(v[i]); 192 193 poly.translate(centre.getX(), centre.getY()); 194 195 return poly; 196 } 197 198 /** 199 * Set the x-ordinate of the centre of the circle. 200 * 201 * @param x 202 * The x-ordinate 203 */ 204 public void setX(float x) { 205 this.centre.setX(x); 206 } 207 208 /** 209 * Set the y-ordinate of the centre of the circle. 210 * 211 * @param y 212 * The y-ordinate 213 */ 214 public void setY(float y) { 215 this.centre.setY(y); 216 } 217 218 /** 219 * Set the radius of the circle. 220 * 221 * @param r 222 * The radius. 223 */ 224 public void setRadius(float r) { 225 this.radius = r; 226 } 227 228 /** 229 * @return The x-ordinate of the centre 230 */ 231 public float getX() { 232 return centre.getX(); 233 } 234 235 /** 236 * @return The y-ordinate of the centre 237 */ 238 public float getY() { 239 return centre.getY(); 240 } 241 242 /** 243 * @return The radius of the circle 244 */ 245 public float getRadius() { 246 return radius; 247 } 248 249 @Override 250 public double intersectionArea(Shape that) { 251 return intersectionArea(that, 100); 252 } 253 254 @Override 255 public String toString() { 256 return String.format("Circle (%f, %f, %f)", this.centre.getX(), this.centre.getY(), this.radius); 257 } 258 259 @Override 260 public double intersectionArea(Shape that, int nStepsPerDimention) { 261 final Rectangle overlapping = this.calculateRegularBoundingBox().overlapping(that.calculateRegularBoundingBox()); 262 if (overlapping == null) 263 return 0; 264 double intersection = 0; 265 final double step = Math.max(overlapping.width, overlapping.height) / (double) nStepsPerDimention; 266 double nReads = 0; 267 for (float x = overlapping.x; x < overlapping.x + overlapping.width; x += step) { 268 for (float y = overlapping.y; y < overlapping.y + overlapping.height; y += step) { 269 final boolean insideThis = this.isInside(new Point2dImpl(x, y)); 270 final boolean insideThat = that.isInside(new Point2dImpl(x, y)); 271 nReads++; 272 if (insideThis && insideThat) { 273 intersection++; 274 } 275 } 276 } 277 278 return (intersection / nReads) * (overlapping.width * overlapping.height); 279 } 280 281 @Override 282 public Circle clone() { 283 return new Circle(this.centre.copy(), this.radius); 284 } 285 286 @Override 287 public boolean equals(Object obj) { 288 if (!(obj instanceof Circle)) 289 return false; 290 291 final Circle that = (Circle) obj; 292 return that.centre.equals(this.centre) && that.radius == this.radius; 293 } 294 295 @Override 296 public double calculatePerimeter() { 297 return Math.PI * 2 * this.radius; 298 } 299 300 @Override 301 public RotatedRectangle minimumBoundingRectangle() { 302 return new RotatedRectangle(this.calculateRegularBoundingBox(), 0); 303 } 304 305 @Override 306 public boolean isConvex() { 307 return true; 308 } 309}