/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.arakhne.afc.math.discrete.object2d; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeSet; import org.arakhne.afc.math.generic.PathWindingRule; import org.arakhne.afc.math.generic.Point2D; import org.arakhne.afc.math.matrix.Transform2D; /** 2D circle with integer coordinates. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @deprecated see {@link org.arakhne.afc.math.geometry.d2.i.Circle2i} */ @Deprecated @SuppressWarnings("all") public class Circle2i extends AbstractShape2i<Circle2i> { private static final long serialVersionUID = -2396327310912728347L; /** * ArcIterator.btan(Math.PI/2) */ static final float CTRL_VAL = 0.5522847498307933f; /** * ctrlpts contains the control points for a set of 4 cubic * bezier curves that approximate a circle of radius 0.5 * centered at 0.5, 0.5 */ static final float PCV = 0.5f + CTRL_VAL * 0.5f; /** * ctrlpts contains the control points for a set of 4 cubic * bezier curves that approximate a circle of radius 0.5 * centered at 0.5, 0.5 */ static final float NCV = 0.5f - CTRL_VAL * 0.5f; /** * ctrlpts contains the control points for a set of 4 cubic * bezier curves that approximate a circle of radius 0.5 * centered at 0.5, 0.5 */ static float CTRL_PTS[][] = { { 1.0f, PCV, PCV, 1.0f, 0.5f, 1.0f }, { NCV, 1.0f, 0.0f, PCV, 0.0f, 0.5f }, { 0.0f, NCV, NCV, 0.0f, 0.5f, 0.0f }, { PCV, 0.0f, 1.0f, NCV, 1.0f, 0.5f } }; /** Replies if the given point is inside the circle. * * @param cx is the x-coordinate of the circle center * @param cy is the y-coordinate of the circle center * @param cr is the radius of the circle center * @param x is the x-coordinate of the point * @param y is the y-coordinate of the point * @return <code>true</code> if the point is inside the circle. */ public static boolean contains(int cx, int cy, int cr, int x, int y) { int vx = x - cx; int vy = y - cy; if (vx>=-cr && vx<=cr && vy>=-cr && vy<=cr) { int octant; boolean xpos = (vx>=0); boolean ypos = (vy>=0); if (xpos) { if (ypos) { octant = 0; } else { octant = 2; } } else { if (ypos) { octant = 6; } else { octant = 4; } } int px, py, ccw, cpx, cpy; boolean allNull = true; Point2i p; CirclePerimeterIterator iterator = new CirclePerimeterIterator( cx, cy, cr, octant, octant+2, false); while (iterator.hasNext()) { p = iterator.next(); // Trivial case if (p.x()==x && p.y()==y) return true; px = cy - p.y(); py = p.x() - cx; cpx = x - p.x(); cpy = y - p.y(); ccw = cpx * py - cpy * px; if (ccw>0) return false; if (ccw<0) allNull = false; } return !allNull; } return false; } /** Replies if the given point is inside the quadrant of the given circle. * * <table border="1" width="100%" summary="definition of the quadrant values"> * <thead> * <tr><td>quadrant</td><td>x</td><td>y</td></tr> * </thead> * <tbody> * <tr><td>0</td><td>≥cx</td><td>≥cy</td></tr> * <tr><td>1</td><td>≥cx</td><td><cy</td></tr> * <tr><td>2</td><td><cx</td><td>≥cy</td></tr> * <tr><td>3</td><td><cx</td><td><cy</td></tr> * </tbody> * </table> * * @param cx is the x-coordinate of the circle center * @param cy is the y-coordinate of the circle center * @param cr is the radius of the circle center * @param quadrant is the quadrant, see table in the method description. * @param x is the x-coordinate of the point * @param y is the y-coordinate of the point * @return <code>true</code> if the point is inside the circle. */ public static boolean contains(int cx, int cy, int cr, int quadrant, int x, int y) { int vx = x - cx; int vy = y - cy; if (vx>=-cr && vx<=cr && vy>=-cr && vy<=cr) { int octant; boolean xpos = (vx>=0); boolean ypos = (vy>=0); if (xpos) { if (ypos) { octant = 0; } else { octant = 2; } } else { if (ypos) { octant = 6; } else { octant = 4; } } if (quadrant*2!=octant) return false; int px, py, ccw, cpx, cpy; Point2i p; CirclePerimeterIterator iterator = new CirclePerimeterIterator( cx, cy, cr, octant, octant+2, false); while (iterator.hasNext()) { p = iterator.next(); px = cy - p.y(); py = p.x() - cx; cpx = x - p.x(); cpy = y - p.y(); ccw = cpx * py - cpy * px; if (ccw>0) return false; } return true; } return false; } /** Replies the closest point in a circle to a point. * * @param cx is the center of the circle * @param cy is the center of the circle * @param cr is the radius of the circle * @param x is the point * @param y is the point * @return the closest point in the circle to the point. */ public static Point2i computeClosestPointTo(int cx, int cy, int cr, int x, int y) { int vx = x - cx; int vy = y - cy; int octant; boolean xpos = (vx>=0); boolean ypos = (vy>=0); if (xpos) { if (ypos) { octant = 0; } else { octant = 2; } } else { if (ypos) { octant = 6; } else { octant = 4; } } int d, px, py, cpx, cpy, ccw; Point2i p; CirclePerimeterIterator iterator = new CirclePerimeterIterator( cx, cy, cr, octant, octant+2, false); boolean isInside = true; int minDist = Integer.MAX_VALUE; Point2i close = new Point2i(); while (iterator.hasNext()) { p = iterator.next(); px = cy - p.y(); py = p.x() - cx; cpx = x - p.x(); cpy = y - p.y(); ccw = cpx * py - cpy * px; if (ccw>=0) { isInside = false; d = cpx*cpx + cpy*cpy; if (d<minDist) { minDist = d; close.set(p); } } } // inside the circle if (isInside) close.set(x,y); return close; } /** Replies if two circles are intersecting. * * @param x1 is the center of the first circle * @param y1 is the center of the first circle * @param radius1 is the radius of the first circle * @param x2 is the center of the second circle * @param y2 is the center of the second circle * @param radius2 is the radius of the second circle * @return <code>true</code> if the two shapes are intersecting; otherwise * <code>false</code> */ public static boolean intersectsCircleCircle(int x1, int y1, int radius1, int x2, int y2, int radius2) { Point2i c = computeClosestPointTo(x1, y1, radius1, x2, y2); return contains(x2, y2, radius2, c.x(), c.y()); } /** Replies if a circle and a rectangle are intersecting. * * @param x1 is the center of the circle * @param y1 is the center of the circle * @param radius is the radius of the circle * @param x2 is the first corner of the rectangle. * @param y2 is the first corner of the rectangle. * @param x3 is the second corner of the rectangle. * @param y3 is the second corner of the rectangle. * @return <code>true</code> if the two shapes are intersecting; otherwise * <code>false</code> */ public static boolean intersectsCircleRectangle(int x1, int y1, int radius, int x2, int y2, int x3, int y3) { Point2i c = Rectangle2i.computeClosestPoint(x2, y2, x3, y3, x1, y1); return contains(x1, y1, radius, c.x(), c.y()); } /** Replies if a circle and a segment are intersecting. * * @param x1 is the center of the circle * @param y1 is the center of the circle * @param radius is the radius of the circle * @param x2 is the first point of the segment. * @param y2 is the first point of the segment. * @param x3 is the second point of the segment. * @param y3 is the second point of the segment. * @return <code>true</code> if the two shapes are intersecting; otherwise * <code>false</code> */ public static boolean intersectsCircleSegment(int x1, int y1, int radius, int x2, int y2, int x3, int y3) { Point2i p = Segment2i.computeClosestPointTo(x2, y2, x3, y3, x1, y1); return contains(x1, y1, radius, p.x(), p.y()); } /** X-coordinate of the center of the circle. */ protected int cx = 0; /** Y-coordinate of the center of the circle. */ protected int cy = 0; /** Radius of the circle. */ protected int radius = 0; /** */ public Circle2i() { // } /** * @param center * @param radius */ public Circle2i(Point2D center, int radius) { set(center, radius); } /** * @param x * @param y * @param radius */ public Circle2i(int x, int y, int radius) { set(x, y, radius); } /** * @param c * */ public Circle2i(Circle2i c) { this.cx = c.cx; this.cy = c.cy; this.radius = c.radius; } /** {@inheritDoc} */ @Override public void clear() { this.cx = this.cy = 0; this.radius = 0; } /** Replies if this circle is empty. * The circle is empty when the radius is nul. * * @return <code>true</code> if the radius is nul; * otherwise <code>false</code>. */ @Override public boolean isEmpty() { return this.radius<=0; } /** Change the frame of the circle. * * @param x * @param y * @param radius */ public void set(int x, int y, int radius) { this.cx = x; this.cy = y; this.radius = Math.abs(radius); } @Override public void set(Shape2i s) { Rectangle2i r = s.toBoundingBox(); this.cx = r.getCenterX(); this.cy = r.getCenterY(); this.radius = Math.min(r.getWidth(), r.getHeight()) / 2; } /** Change the frame of te circle. * * @param center * @param radius */ public void set(Point2D center, int radius) { this.cx = center.x(); this.cy = center.y(); this.radius = Math.abs(radius); } /** Replies the center X. * * @return the center x. */ public int getX() { return this.cx; } /** Replies the center y. * * @return the center y. */ public int getY() { return this.cy; } /** Replies the center. * * @return a copy of the center. */ public Point2i getCenter() { return new Point2i(this.cx, this.cy); } /** Replies the radius. * * @return the radius. */ public int getRadius() { return this.radius; } @Override public Rectangle2i toBoundingBox() { Rectangle2i r = new Rectangle2i(); r.setFromCorners( this.cx-this.radius, this.cy-this.radius, this.cx+this.radius, this.cy+this.radius); return r; } @Override public void toBoundingBox(Rectangle2i box) { box.setFromCorners( this.cx-this.radius, this.cy-this.radius, this.cx+this.radius, this.cy+this.radius); } /** {@inheritDoc} */ @Override public float distanceSquared(Point2D p) { Point2i c = getClosestPointTo(p); return c.distanceSquared(p); } /** {@inheritDoc} */ @Override public float distanceL1(Point2D p) { Point2i c = getClosestPointTo(p); return c.distanceL1(p); } /** {@inheritDoc} */ @Override public float distanceLinf(Point2D p) { Point2i c = getClosestPointTo(p); return c.distanceLinf(p); } /** {@inheritDoc} */ @Override public Point2i getClosestPointTo(Point2D p) { return computeClosestPointTo(this.cx, this.cy, this.radius, p.x(), p.y()); } @Override public boolean intersects(Rectangle2i s) { return intersectsCircleRectangle( getX(), getY(), getRadius(), s.getMinX(), s.getMinY(), s.getMaxX(), s.getMaxY()); } @Override public boolean intersects(Circle2i s) { return intersectsCircleCircle( getX(), getY(), getRadius(), s.getX(), s.getY(), s.getRadius()); } @Override public boolean intersects(Segment2i s) { return intersectsCircleSegment( getX(), getY(), getRadius(), s.getX1(), s.getY1(), s.getX2(), s.getY2()); } @Override public Shape2i createTransformedShape(Transform2D transform) { if (transform==null || transform.isIdentity()) return clone(); return new Path2i(getPathIterator(transform)); } @Override public void translate(int dx, int dy) { this.cx += dx; this.cy += dy; } @Override public boolean contains(int x, int y) { return contains(this.cx, this.cy, this.radius, x, y); } private static void m(int[] quadrants, int k, int x, int y) { if (x>0) { if (y>0) { quadrants[0] |= k; } else { quadrants[1] |= k; } } else { if (y>0) { quadrants[3] |= k; } else { quadrants[2] |= k; } } } @Override public boolean contains(Rectangle2i r) { int vx1 = r.getMinX() - this.cx; int vy1 = r.getMinY() - this.cy; int vx2 = r.getMaxX() - this.cx; int vy2 = r.getMaxY() - this.cy; if (vx1>=-this.radius && vx1<=this.radius && vy1>=-this.radius && vy1<=this.radius && vx2>=-this.radius && vx2<=this.radius && vy2>=-this.radius && vy2<=this.radius) { int[] quadrants = new int[4]; int[] x = new int[] {vx1, vx2, vx2, vx1}; int[] y = new int[] {vy1, vy1, vy2, vy2}; for(int i=0; i<4; ++i) { m(quadrants, (1<<i), x[i], y[i]); } for(int i=0; i<quadrants.length; ++i) { if (quadrants[i]!=0) { CirclePerimeterIterator iterator = new CirclePerimeterIterator( this.cx, this.cy, this.radius, i*2, i*2+2, false); int px, py, ccw, cpx, cpy; Point2i p; while (iterator.hasNext()) { p = iterator.next(); px = this.cy - p.y(); py = p.x() - this.cx; for(int j=0; j<4; ++j) { if ((quadrants[i] & (1<<j))!=0) { cpx = x[j] - p.x(); cpy = y[j] - p.y(); ccw = cpx * py - cpy * px; if (ccw>0) return false; } } } } } return true; } return false; } /** Replies the points of the circle perimeters starting by the first octant. * * @return the points on the perimeters. */ @Override public Iterator<Point2i> getPointIterator() { return new CirclePerimeterIterator(this.cx, this.cy, this.radius, 0, 8, true); } /** Replies the points of the circle perimeters starting by the first octant. * * @param firstOctantIndex is the index of the first octant (see figure) to treat. * @param nbOctants is the number of octants to traverse (greater than zero). * @return the points on the perimeters. */ public Iterator<Point2i> getPointIterator(int firstOctantIndex, int nbOctants) { return getPointIterator(this.cx, this.cy, this.radius, firstOctantIndex, nbOctants); } /** Replies the points of the circle perimeters starting by the first octant. * * @param cx is the center of the radius. * @param cy is the center of the radius. * @param radius is the radius of the radius. * @param firstOctantIndex is the index of the first octant (see figure) to treat. * @param nbOctants is the number of octants to traverse (greater than zero). * @return the points on the perimeters. */ public static Iterator<Point2i> getPointIterator(int cx, int cy, int radius, int firstOctantIndex, int nbOctants) { int startOctant, maxOctant; if (firstOctantIndex<=0) startOctant = 0; else if (firstOctantIndex>=8) startOctant = 7; else startOctant = firstOctantIndex; maxOctant = startOctant + nbOctants; if (maxOctant>8) maxOctant = 8; return new CirclePerimeterIterator( cx, cy, radius, startOctant, maxOctant, true); } @Override public PathIterator2i getPathIterator(Transform2D transform) { if (transform==null || transform.isIdentity()) return new CopyPathIterator(this.cx, this.cy, this.radius); return new TransformPathIterator(this.cx, this.cy, this.radius, transform); } /** Iterates on points on the perimeter of a circle. * <p> * The rastrerization is based on a Bresenham algorithm. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private static class CirclePerimeterIterator implements Iterator<Point2i> { private final int cx; private final int cy; private final int cr; private final boolean skip; private final int maxOctant; private int currentOctant; private int x, y, d; private Point2i next = null; private final Set<Point2i> junctionPoint = new TreeSet<>(new Tuple2iComparator()); /** * @param x * @param y * @param r * @param initialOctant * @param maxOctant * @param skip */ public CirclePerimeterIterator(int x, int y, int r, int initialOctant, int maxOctant, boolean skip) { assert(r>=0); this.cx = x; this.cy = y; this.cr = r; this.skip = skip; this.maxOctant = maxOctant; this.currentOctant = initialOctant; reset(); searchNext(); } private void reset() { this.x = 0; this.y = this.cr; this.d = 3 - 2 * this.cr; if (this.skip && (this.currentOctant==3 || this.currentOctant==4 || this.currentOctant==6 || this.currentOctant==7)) { // skip the first point because already replied in previous octant if (this.d<=0) { this.d += 4 * this.x + 6; } else { this.d += 4 * (this.x - this.y) + 10; --this.y; } ++this.x; } } /** * {@inheritDoc} */ @Override public boolean hasNext() { return this.next!=null; } private void searchNext() { if (this.currentOctant>=this.maxOctant) { this.next = null; } else { this.next = new Point2i(); while (true) { switch(this.currentOctant) { case 0: this.next.set(this.cx + this.x, this.cy + this.y); break; case 1: this.next.set(this.cx + this.y, this.cy + this.x); break; case 2: this.next.set(this.cx + this.x, this.cy - this.y); break; case 3: this.next.set(this.cx + this.y, this.cy - this.x); break; case 4: this.next.set(this.cx - this.x, this.cy - this.y); break; case 5: this.next.set(this.cx - this.y, this.cy - this.x); break; case 6: this.next.set(this.cx - this.x, this.cy + this.y); break; case 7: this.next.set(this.cx - this.y, this.cy + this.x); break; default: throw new NoSuchElementException(); } if (this.d<=0) { this.d += 4 * this.x + 6; } else { this.d += 4 * (this.x - this.y) + 10; --this.y; } ++this.x; if (this.x>this.y) { // The octant is finished. // Save the junction. boolean cont = this.junctionPoint.contains(this.next); if (!cont) this.junctionPoint.add(new Point2i(this.next)); // Goto next. ++this.currentOctant; reset(); if (this.currentOctant>=this.maxOctant) { if (cont) this.next = null; cont = false; } if (!cont) return; } else { return; } } } } /** * {@inheritDoc} */ @Override public Point2i next() { Point2i pixel = this.next; if (pixel==null) throw new NoSuchElementException(); searchNext(); return pixel; } /** * {@inheritDoc} */ @Override public void remove() { throw new UnsupportedOperationException(); } } // class CirclePerimeterIterator /** Iterator on the path elements of the circle. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private static class CopyPathIterator implements PathIterator2i { private final int x; private final int y; private final int r; private int index = 0; private int movex, movey; private int lastx, lasty; /** * @param x * @param y * @param r */ public CopyPathIterator(int x, int y, int r) { this.r = Math.max(0, r); this.x = x - this.r; this.y = y - this.r; if (this.r<=0f) { this.index = 6; } } @Override public boolean hasNext() { return this.index<=5; } @Override public PathElement2i next() { if (this.index>5) throw new NoSuchElementException(); int idx = this.index; ++this.index; if (idx==0) { int dr = 2 * this.r; float ctrls[] = CTRL_PTS[3]; this.movex = (int)(this.x + ctrls[4] * dr); this.movey = (int)(this.y + ctrls[5] * dr); this.lastx = this.movex; this.lasty = this.movey; return new PathElement2i.MovePathElement2i( this.lastx, this.lasty); } else if (idx<5) { int dr = 2 * this.r; float ctrls[] = CTRL_PTS[idx - 1]; int ppx = this.lastx; int ppy = this.lasty; this.lastx = (int)(this.x + ctrls[4] * dr); this.lasty = (int)(this.y + ctrls[5] * dr); return new PathElement2i.CurvePathElement2i( ppx, ppy, (int)(this.x + ctrls[0] * dr), (int)(this.y + ctrls[1] * dr), (int)(this.x + ctrls[2] * dr), (int)(this.y + ctrls[3] * dr), this.lastx, this.lasty); } int ppx = this.lastx; int ppy = this.lasty; this.lastx = this.movex; this.lasty = this.movey; return new PathElement2i.ClosePathElement2i( ppx, ppy, this.lastx, this.lasty); } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public PathWindingRule getWindingRule() { return PathWindingRule.NON_ZERO; } @Override public boolean isPolyline() { return false; } } /** Iterator on the path elements of the circle. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private static class TransformPathIterator implements PathIterator2i { private final Point2D p1 = new Point2i(); private final Point2D p2 = new Point2i(); private final Point2D ptmp1 = new Point2i(); private final Point2D ptmp2 = new Point2i(); private final Transform2D transform; private final int x; private final int y; private final int r; private int index = 0; private int movex, movey; /** * @param x * @param y * @param r * @param transform */ public TransformPathIterator(int x, int y, int r, Transform2D transform) { assert(transform!=null); this.transform = transform; this.r = Math.max(0, r); this.x = x - this.r; this.y = y - this.r; if (this.r<=0f) { this.index = 6; } } @Override public boolean hasNext() { return this.index<=5; } @Override public PathElement2i next() { if (this.index>5) throw new NoSuchElementException(); int idx = this.index; ++this.index; if (idx==0) { int dr = 2 * this.r; float ctrls[] = CTRL_PTS[3]; this.movex = (int)(this.x + ctrls[4] * dr); this.movey = (int)(this.y + ctrls[5] * dr); this.p2.set(this.movex, this.movey); this.transform.transform(this.p2); return new PathElement2i.MovePathElement2i( this.p2.x(), this.p2.y()); } else if (idx<5) { int dr = 2 * this.r; float ctrls[] = CTRL_PTS[idx - 1]; this.p1.set(this.p2); this.p2.set( (this.x + ctrls[4] * dr), (this.y + ctrls[5] * dr)); this.transform.transform(this.p2); this.ptmp1.set( (this.x + ctrls[0] * dr), (this.y + ctrls[1] * dr)); this.transform.transform(this.ptmp1); this.ptmp2.set( (this.x + ctrls[2] * dr), (this.y + ctrls[3] * dr)); this.transform.transform(this.ptmp2); return new PathElement2i.CurvePathElement2i( this.p1.x(), this.p1.y(), this.ptmp1.x(), this.ptmp1.y(), this.ptmp2.x(), this.ptmp2.y(), this.p2.x(), this.p2.y()); } this.p1.set(this.p2); this.p2.set(this.movex, this.movey); this.transform.transform(this.p2); return new PathElement2i.ClosePathElement2i( this.p1.x(), this.p1.y(), this.p2.x(), this.p2.y()); } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public PathWindingRule getWindingRule() { return PathWindingRule.NON_ZERO; } @Override public boolean isPolyline() { return false; } } }