/*
* $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.continous.object2d;
import java.util.NoSuchElementException;
import org.arakhne.afc.math.MathConstants;
import org.arakhne.afc.math.generic.PathElementType;
import org.arakhne.afc.math.generic.PathWindingRule;
import org.arakhne.afc.math.generic.Point2D;
import org.arakhne.afc.math.geometry.d2.afp.Ellipse2afp;
import org.arakhne.afc.math.geometry.d2.d.Point2d;
import org.arakhne.afc.math.geometry.d2.d.RoundRectangle2d;
import org.arakhne.afc.math.matrix.Transform2D;
import org.arakhne.afc.vmutil.ReflectionUtil;
/** 2D round rectangle with floating-point points.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @deprecated see {@link RoundRectangle2d}
*/
@Deprecated
@SuppressWarnings("all")
public class RoundRectangle2f extends AbstractRectangularShape2f<RoundRectangle2f> {
private static final long serialVersionUID = 4681356809053380781L;
/** Replies if a rectangle is inside in the round rectangle.
*
* @param rx1 is the lowest corner of the round rectangle.
* @param ry1 is the lowest corner of the round rectangle.
* @param rwidth1 is the width of the round rectangle.
* @param rheight1 is the height of the round rectangle.
* @param awidth is the width of the arc of the round rectangle.
* @param aheight is the height of the arc of the round rectangle.
* @param rx2 is the lowest corner of the inner-candidate rectangle.
* @param ry2 is the lowest corner of the inner-candidate rectangle.
* @param rwidth2 is the width of the inner-candidate rectangle.
* @param rheight2 is the height of the inner-candidate rectangle.
* @return <code>true</code> if the given rectangle is inside the ellipse;
* otherwise <code>false</code>.
*/
public static boolean containsRoundRectangleRectangle(float rx1, float ry1, float rwidth1, float rheight1, float awidth, float aheight, float rx2, float ry2, float rwidth2, float rheight2) {
float rcx1 = (rx1 + rwidth1/2f);
float rcy1 = (ry1 + rheight1/2f);
float rcx2 = (rx2 + rwidth2/2f);
float rcy2 = (ry2 + rheight2/2f);
float farX;
if (rcx1<=rcx2) farX = rx2 + rwidth2;
else farX = rx2;
float farY;
if (rcy1<=rcy2) farY = ry2 + rheight2;
else farY = ry2;
return containsRoundRectanglePoint(rx1, ry1, rwidth1, rheight1, awidth, aheight, farX, farY);
}
/** Replies if a point is inside in the round rectangle.
*
* @param rx is the lowest corner of the round rectangle.
* @param ry is the lowest corner of the round rectangle.
* @param rwidth is the width of the round rectangle.
* @param rheight is the height of the round rectangle.
* @param awidth is the width of the arc of the round rectangle.
* @param aheight is the height of the arc of the round rectangle.
* @param px is the point.
* @param py is the point.
* @return <code>true</code> if the given rectangle is inside the ellipse;
* otherwise <code>false</code>.
*/
public static boolean containsRoundRectanglePoint(float rx, float ry, float rwidth, float rheight, float awidth, float aheight, float px, float py) {
if (rwidth<=0f && rheight<=0f) {
return rx==px && ry==py;
}
float rx0 = rx;
float ry0 = ry;
float rrx1 = rx0 + rwidth;
float rry1 = ry0 + rheight;
// Check for trivial rejection - point is outside bounding rectangle
if (px < rx0 || py < ry0 || px >= rrx1 || py >= rry1) {
return false;
}
float aw = Math.min(rwidth, Math.abs(awidth)) / 2f;
float ah = Math.min(rheight, Math.abs(aheight)) / 2f;
// Check which corner point is in and do circular containment
// test - otherwise simple acceptance
if (px >= (rx0 += aw) && px < (rx0 = rrx1 - aw)) {
return true;
}
if (py >= (ry0 += ah) && py < (ry0 = rry1 - ah)) {
return true;
}
float xx = (px - rx0) / aw;
float yy = (py - ry0) / ah;
return (xx * xx + yy * yy <= 1f);
}
private static final float ANGLE = (float) MathConstants.PI / 4f;
private static final float A = 1f - (float)Math.cos(ANGLE);
private static final float B = (float)Math.tan(ANGLE);
private static final float C = (float)Math.sqrt(1f + B * B) - 1f + A;
private static final float CV = 4f / 3f * A * B / C;
private static final float ACV = (1f - CV) / 2f;
/** For each array:
* 4 values for each point {v0, v1, v2, v3}:
* point = (x + v0 * w + v1 * arcWidth,
* y + v2 * h + v3 * arcHeight)
*/
static float CTRL_PTS[][] = {
{ 0f, 0f, 0f, .5f },
{ 0f, 0f, 1f, -.5f },
{ 0f, 0f, 1f, -ACV,
0f, ACV, 1f, 0f,
0f, .5f, 1f, 0f },
{ 1f, -.5f, 1f, 0f },
{ 1f, -ACV, 1f, 0f,
1f, 0f, 1f, -ACV,
1f, 0f, 1f, -.5f },
{ 1f, 0f, 0f, .5f },
{ 1f, 0f, 0f, ACV,
1f, -ACV, 0f, 0f,
1f, -.5f, 0f, 0f },
{ 0f, .5f, 0f, 0f },
{ 0f, ACV, 0f, 0f,
0f, 0f, 0f, ACV,
0f, 0f, 0f, .5f },
{},
};
/** Types of path elements for the round rectangle.
*/
static PathElementType TYPES[] = {
PathElementType.MOVE_TO,
PathElementType.LINE_TO, PathElementType.CURVE_TO,
PathElementType.LINE_TO, PathElementType.CURVE_TO,
PathElementType.LINE_TO, PathElementType.CURVE_TO,
PathElementType.LINE_TO, PathElementType.CURVE_TO,
PathElementType.CLOSE,
};
/** Width of the arcs at the corner of the box. */
protected float arcWidth;
/** Height of the arcs at the corner of the box. */
protected float arcHeight;
/**
*/
public RoundRectangle2f() {
this.arcHeight = this.arcWidth = 0f;
}
/**
* @param min is the min corner of the rectangle.
* @param max is the max corner of the rectangle.
* @param arcWidth
* @param arcHeight
*/
public RoundRectangle2f(Point2f min, Point2f max, float arcWidth, float arcHeight) {
super(min, max);
this.arcWidth = arcWidth;
this.arcHeight = arcHeight;
}
/**
* @param rr
*/
public RoundRectangle2f(RoundRectangle2f rr) {
super(rr);
}
/**
* @param x
* @param y
* @param width
* @param height
* @param arcWidth
* @param arcHeight
*/
public RoundRectangle2f(float x, float y, float width, float height, float arcWidth, float arcHeight) {
super(x, y, width, height);
this.arcWidth = arcWidth;
this.arcHeight = arcHeight;
}
@Override
public void clear() {
this.arcHeight = this.arcWidth = 0f;
super.clear();
}
/**
* Gets the width of the arc that rounds off the corners.
* @return the width of the arc that rounds off the corners
* of this <code>RoundRectangle2f</code>.
*/
public float getArcWidth() {
return this.arcWidth;
}
/**
* Gets the height of the arc that rounds off the corners.
* @return the height of the arc that rounds off the corners
* of this <code>RoundRectangle2f</code>.
*/
public float getArcHeight() {
return this.arcHeight;
}
/**
* Set the width of the arc that rounds off the corners.
* @param a is the width of the arc that rounds off the corners
* of this <code>RoundRectangle2f</code>.
*/
public void setArcWidth(float a) {
this.arcWidth = a;
}
/**
* Set the height of the arc that rounds off the corners.
* @param a is the height of the arc that rounds off the corners
* of this <code>RoundRectangle2f</code>.
*/
public void setArcHeight(float a) {
this.arcHeight = a;
}
/** Change the frame of the rectangle.
*
* @param x
* @param y
* @param width
* @param height
* @param arcWidth is the width of the arc that rounds off the corners
* of this <code>RoundRectangle2f</code>.
* @param arcHeight is the height of the arc that rounds off the corners
* of this <code>RoundRectangle2f</code>.
*/
public void set(float x, float y, float width, float height, float arcWidth, float arcHeight) {
setFromCorners(x, y, x+width, y+height);
this.arcWidth = arcWidth;
this.arcHeight = arcHeight;
}
/** {@inheritDoc}
*/
@Override
public boolean contains(float x, float y) {
return containsRoundRectanglePoint(
getMinX(), getMinY(), getWidth(), getHeight(), getArcWidth(), getArcHeight(),
x, y);
}
@Override
public boolean contains(Rectangle2f r) {
return containsRoundRectangleRectangle(
getMinX(), getMinY(), getWidth(), getHeight(), getArcWidth(), getArcHeight(),
r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight());
}
@Override
public float distanceSquared(Point2D p) {
Point2D n = getClosestPointTo(p);
return n.distanceSquared(p);
}
@Override
public float distanceL1(Point2D p) {
Point2D n = getClosestPointTo(p);
return n.distanceL1(p);
}
@Override
public float distanceLinf(Point2D p) {
Point2D n = getClosestPointTo(p);
return n.distanceLinf(p);
}
@Override
public Point2D getClosestPointTo(Point2D p) {
float px = p.getX();
float py = p.getY();
float rx1 = getMinX();
float ry1 = getMinY();
float rx2 = getMaxX();
float ry2 = getMaxY();
float aw = getArcWidth();
float ah = getArcHeight();
int same = 0;
float x, y;
if (px<rx1+aw) {
if (py<ry1+ah) {
Point2d pts = new Point2d();
Ellipse2afp.findsClosestPointSolidEllipsePoint(
px, py,
rx1, ry1,
aw, ah,
pts);
return new Point2f(pts.getX(), pts.getY());
}
if (py>ry2-ah) {
Point2d pts = new Point2d();
Ellipse2afp.findsClosestPointSolidEllipsePoint(
px, py,
rx1, ry2-ah,
aw, ah,
pts);
return new Point2f(pts.getX(), pts.getY());
}
}
else if (px>rx2-aw) {
if (py<ry1+ah) {
Point2d pts = new Point2d();
Ellipse2afp.findsClosestPointSolidEllipsePoint(
px, py,
rx2-aw, ry1,
aw, ah,
pts);
return new Point2f(pts.getX(), pts.getY());
}
if (py>ry2-ah) {
Point2d pts = new Point2d();
Ellipse2afp.findsClosestPointSolidEllipsePoint(
px, py,
rx2-aw, ry2-ah,
aw, ah,
pts);
return new Point2f(pts.getX(), pts.getY());
}
}
if (px<rx1) {
x = rx1;
}
else if (px>rx2) {
x = rx2;
}
else {
x = px;
++same;
}
if (py<ry1) {
y = ry1;
}
else if (py>ry2) {
y = ry2;
}
else {
y = py;
++same;
}
if (same==2) return p;
return new Point2f(x,y);
}
@Override
public PathIterator2f getPathIterator(Transform2D transform) {
if (transform==null) {
return new CopyPathIterator(
getMinX(), getMinY(),
getWidth(), getHeight(),
getArcWidth(), getArcHeight());
}
return new TransformPathIterator(
getMinX(), getMinY(),
getWidth(), getHeight(),
getArcWidth(), getArcHeight(),
transform);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof RoundRectangle2f) {
RoundRectangle2f rr2d = (RoundRectangle2f) obj;
return ((getMinX() == rr2d.getMinX()) &&
(getMinY() == rr2d.getMinY()) &&
(getWidth() == rr2d.getWidth()) &&
(getHeight() == rr2d.getHeight()) &&
(getArcWidth() == rr2d.getArcWidth()) &&
(getArcHeight() == rr2d.getArcHeight()));
}
return false;
}
@Override
public int hashCode() {
long bits = 1L;
bits = 31L * bits + floatToIntBits(getMinX());
bits = 31L * bits + floatToIntBits(getMinY());
bits = 31L * bits + floatToIntBits(getMaxX());
bits = 31L * bits + floatToIntBits(getMaxY());
bits = 31L * bits + floatToIntBits(getArcWidth());
bits = 31L * bits + floatToIntBits(getArcHeight());
return (int) (bits ^ (bits >> 32));
}
@Override
public boolean intersects(Rectangle2f s) {
return Rectangle2f.intersectsRectangleRectangle(
getMinX(), getMinY(),
getMaxX(), getMaxY(),
s.getMinX(), s.getMinY(),
s.getMaxX(), s.getMaxY());
}
@Override
public boolean intersects(Ellipse2f s) {
return Ellipse2f.intersectsEllipseRectangle(
s.getMinX(), s.getMinY(),
s.getMaxX(), s.getMaxY(),
getMinX(), getMinY(),
getMaxX(), getMaxY());
}
@Override
public boolean intersects(Circle2f s) {
return Circle2f.intersectsCircleRectangle(
s.getX(), s.getY(),
s.getRadius(),
getMinX(), getMinY(),
getMaxX(), getMaxY());
}
@Override
public boolean intersects(Segment2f s) {
return Rectangle2f.intersectsRectangleSegment(
getMinX(), getMinY(),
getMaxX(), getMaxY(),
s.getX1(), s.getY1(),
s.getX2(), s.getY2());
}
@Override
public boolean intersects(Path2f s) {
return intersects(s.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO));
}
@Override
public boolean intersects(PathIterator2f s) {
// Copied from AWT API
if (isEmpty()) return false;
int mask = (s.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2);
int crossings = Path2f.computeCrossingsFromRect(
s,
getMinX(), getMinY(), getMaxX(), getMaxY(),
false, true);
return (crossings == MathConstants.SHAPE_INTERSECTS ||
(crossings & mask) != 0);
}
@Override
public String toString() {
return ReflectionUtil.toString(this);
}
/** Iterator on the path elements of the rectangle.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private static class CopyPathIterator implements PathIterator2f {
private final float x;
private final float y;
private final float w;
private final float h;
private final float aw;
private final float ah;
private int index = 0;
private float moveX, moveY, lastX, lastY;
/**
* @param x
* @param y
* @param w
* @param h
* @param aw
* @param ah
*/
public CopyPathIterator(float x, float y, float w, float h, float aw, float ah) {
this.x = x;
this.y = y;
this.w = Math.max(0f, w);
this.h = Math.max(0f, h);
this.aw = Math.min(Math.abs(aw), w);
this.ah = Math.min(Math.abs(ah), h);
if (this.w<=0f || this.h<=0f) {
this.index = TYPES.length;
}
}
@Override
public boolean hasNext() {
return this.index<TYPES.length;
}
@Override
public PathElement2f next() {
if (this.index>=TYPES.length) throw new NoSuchElementException();
int idx = this.index;
PathElement2f element = null;
PathElementType type = TYPES[idx];
float ctrls[] = CTRL_PTS[idx];
float ix, iy;
float ctrlx1, ctrly1, ctrlx2, ctrly2;
switch(type) {
case MOVE_TO:
this.moveX = this.lastX = this.x + ctrls[0] * this.w + ctrls[1] * this.aw;
this.moveY = this.lastY = this.y + ctrls[2] * this.h + ctrls[3] * this.ah;
element = new PathElement2f.MovePathElement2f(
this.lastX, this.lastY);
break;
case LINE_TO:
ix = this.lastX;
iy = this.lastY;
this.lastX = this.x + ctrls[0] * this.w + ctrls[1] * this.aw;
this.lastY = this.y + ctrls[2] * this.h + ctrls[3] * this.ah;
element = new PathElement2f.LinePathElement2f(
ix, iy,
this.lastX, this.lastY);
break;
case CURVE_TO:
ix = this.lastX;
iy = this.lastY;
ctrlx1 = this.x + ctrls[0] * this.w + ctrls[1] * this.aw;
ctrly1 = this.y + ctrls[2] * this.h + ctrls[3] * this.ah;
ctrlx2 = this.x + ctrls[4] * this.w + ctrls[5] * this.aw;
ctrly2 = this.y + ctrls[6] * this.h + ctrls[7] * this.ah;
this.lastX = this.x + ctrls[8] * this.w + ctrls[9] * this.aw;
this.lastY = this.y + ctrls[10] * this.h + ctrls[11] * this.ah;
element = new PathElement2f.CurvePathElement2f(
ix, iy,
ctrlx1, ctrly1,
ctrlx2, ctrly2,
this.lastX, this.lastY);
break;
case CLOSE:
ix = this.lastX;
iy = this.lastY;
this.lastX = this.moveX;
this.lastY = this.moveY;
element = new PathElement2f.ClosePathElement2f(
ix, iy,
this.lastX, this.lastY);
break;
case QUAD_TO:
default:
throw new NoSuchElementException();
}
assert(element!=null);
++this.index;
return element;
}
@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 rectangle.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private static class TransformPathIterator implements PathIterator2f {
private final Transform2D transform;
private final float x;
private final float y;
private final float w;
private final float h;
private final float aw;
private final float ah;
private int index = 0;
private float moveX, moveY;
private final Point2D last = new Point2f();
private final Point2D ctrl1 = new Point2f();
private final Point2D ctrl2 = new Point2f();
/**
* @param x
* @param y
* @param w
* @param h
* @param aw
* @param ah
* @param transform
*/
public TransformPathIterator(float x, float y, float w, float h, float aw, float ah, Transform2D transform) {
this.transform = transform;
this.x = x;
this.y = y;
this.w = Math.max(0f, w);
this.h = Math.max(0f, h);
this.aw = Math.min(Math.abs(aw), w);
this.ah = Math.min(Math.abs(ah), h);
if (this.w<=0f || this.h<=0f) {
this.index = TYPES.length;
}
}
@Override
public boolean hasNext() {
return this.index<TYPES.length;
}
@Override
public PathElement2f next() {
if (this.index>=TYPES.length) throw new NoSuchElementException();
int idx = this.index;
PathElement2f element = null;
PathElementType type = TYPES[idx];
float ctrls[] = CTRL_PTS[idx];
float ix, iy;
switch(type) {
case MOVE_TO:
this.moveX = this.x + ctrls[0] * this.w + ctrls[1] * this.aw;
this.moveY = this.y + ctrls[2] * this.h + ctrls[3] * this.ah;
this.last.set(this.moveX, this.moveY);
this.transform.transform(this.last);
element = new PathElement2f.MovePathElement2f(
this.last.getX(), this.last.getY());
break;
case LINE_TO:
ix = this.last.getX();
iy = this.last.getY();
this.last.set(
this.x + ctrls[0] * this.w + ctrls[1] * this.aw,
this.y + ctrls[2] * this.h + ctrls[3] * this.ah);
this.transform.transform(this.last);
element = new PathElement2f.LinePathElement2f(
ix, iy,
this.last.getX(), this.last.getY());
break;
case CURVE_TO:
ix = this.last.getX();
iy = this.last.getY();
this.ctrl1.set(
this.x + ctrls[0] * this.w + ctrls[1] * this.aw,
this.y + ctrls[2] * this.h + ctrls[3] * this.ah);
this.transform.transform(this.ctrl1);
this.ctrl2.set(
this.x + ctrls[4] * this.w + ctrls[5] * this.aw,
this.y + ctrls[6] * this.h + ctrls[7] * this.ah);
this.transform.transform(this.ctrl2);
this.last.set(
this.x + ctrls[8] * this.w + ctrls[9] * this.aw,
this.y + ctrls[10] * this.h + ctrls[11] * this.ah);
this.transform.transform(this.last);
element = new PathElement2f.CurvePathElement2f(
ix, iy,
this.ctrl1.getX(), this.ctrl1.getY(),
this.ctrl2.getX(), this.ctrl2.getY(),
this.last.getX(), this.last.getY());
break;
case CLOSE:
ix = this.last.getX();
iy = this.last.getY();
this.last.set(this.moveX, this.moveY);
this.transform.transform(this.last);
element = new PathElement2f.ClosePathElement2f(
ix, iy,
this.last.getX(), this.last.getY());
break;
case QUAD_TO:
default:
throw new NoSuchElementException();
}
assert(element!=null);
++this.index;
return element;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public PathWindingRule getWindingRule() {
return PathWindingRule.NON_ZERO;
}
@Override
public boolean isPolyline() {
return false;
}
}
}