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}