/*
* $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.geometry.d2.ai;
import java.util.Iterator;
import org.eclipse.xtext.xbase.lib.Pure;
import org.arakhne.afc.math.MathConstants;
import org.arakhne.afc.math.Unefficient;
import org.arakhne.afc.math.geometry.CrossingComputationType;
import org.arakhne.afc.math.geometry.PathWindingRule;
import org.arakhne.afc.math.geometry.d2.Point2D;
import org.arakhne.afc.math.geometry.d2.Shape2D;
import org.arakhne.afc.math.geometry.d2.Transform2D;
import org.arakhne.afc.math.geometry.d2.Vector2D;
import org.arakhne.afc.vmutil.asserts.AssertMessages;
/** 2D shape with 2d floating coordinates.
*
* @param <ST> is the type of the general implementation.
* @param <IT> is the type of the implementation of this shape.
* @param <IE> is the type of the path elements.
* @param <P> is the type of the points.
* @param <V> is the type of the vectors.
* @param <B> is the type of the bounding boxes.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
public interface Shape2ai<
ST extends Shape2ai<?, ?, IE, P, V, B>,
IT extends Shape2ai<?, ?, IE, P, V, B>,
IE extends PathElement2ai,
P extends Point2D<? super P, ? super V>,
V extends Vector2D<? super V, ? super P>,
B extends Rectangle2ai<?, ?, IE, P, V, B>>
extends Shape2D<ST, IT, PathIterator2ai<IE>, P, V, B> {
/** Replies an iterator on the points covered by the perimeter of this shape.
*
* <p>The implementation of the iterator depends on the shape type.
* There is no warranty about the order of the points.
*
* @return an iterator on the points that are located at the perimeter of the shape.
*/
@Pure
Iterator<P> getPointIterator();
@Pure
@Override
default boolean contains(Point2D<?, ?> pt) {
return contains(pt.ix(), pt.iy());
}
/** Replies if the given point is inside this shape.
*
* @param x x coordinate of the point to test.
* @param y y coordinate of the point to test.
* @return <code>true</code> if the given point is inside this
* shape, otherwise <code>false</code>.
*/
@Pure
boolean contains(int x, int y);
/** Replies if the given rectangle is inside this shape.
*
* @param box the rectangle to test.
* @return <code>true</code> if the given box is inside the shape.
*/
@Pure
boolean contains(Rectangle2ai<?, ?, ?, ?, ?, ?> box);
@Pure
@Unefficient
@Override
default boolean contains(Shape2D<?, ?, ?, ?, ?, ?> shape) {
assert shape != null : AssertMessages.notNullParameter();
if (isEmpty()) {
return false;
}
if (shape instanceof Rectangle2ai) {
return contains((Rectangle2ai<?, ?, ?, ?, ?, ?>) shape);
}
final PathIterator2ai<?> iterator = getPathIterator();
final int crossings;
if (shape instanceof Circle2ai) {
final Circle2ai<?, ?, ?, ?, ?, ?> circle = (Circle2ai<?, ?, ?, ?, ?, ?>) shape;
crossings = Path2ai.calculatesCrossingsPathIteratorCircleShadow(
0, iterator,
circle.getX(), circle.getY(), circle.getRadius(),
CrossingComputationType.STANDARD);
} else if (shape instanceof Segment2ai) {
final Segment2ai<?, ?, ?, ?, ?, ?> segment = (Segment2ai<?, ?, ?, ?, ?, ?>) shape;
crossings = Path2ai.calculatesCrossingsPathIteratorSegmentShadow(
0, iterator,
segment.getX1(), segment.getY1(),
segment.getX2(), segment.getY2(),
CrossingComputationType.STANDARD);
} else if (!iterator.isPolygon()) {
// Only a polygon can contain another shape.
return false;
} else {
final int minX;
final int minY;
final int maxX;
final int maxY;
final Shape2D<?, ?, ?, ?, ?, ?> originalBounds = shape.toBoundingBox();
if (originalBounds instanceof Rectangle2ai) {
final Rectangle2ai<?, ?, ?, ?, ?, ?> rect = (Rectangle2ai<?, ?, ?, ?, ?, ?>) originalBounds;
minX = rect.getMinX();
minY = rect.getMinY();
maxX = rect.getMaxX();
maxY = rect.getMaxY();
} else {
assert originalBounds instanceof Rectangle2ai;
final Rectangle2ai<?, ?, ?, ?, ?, ?> rect = (Rectangle2ai<?, ?, ?, ?, ?, ?>) originalBounds;
minX = rect.getMinX();
minY = rect.getMinY();
maxX = rect.getMaxX();
maxY = rect.getMaxY();
}
final PathIterator2ai<?> shapePathIterator = iterator.getGeomFactory().convert(shape.getPathIterator());
crossings = Path2ai.calculatesCrossingsPathIteratorPathShadow(
0, iterator,
new BasicPathShadow2ai(shapePathIterator, minX, minY, maxX, maxY),
CrossingComputationType.STANDARD);
}
final int mask = iterator.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
return crossings != MathConstants.SHAPE_INTERSECTS
&& (crossings & mask) != 0;
}
@Pure
@Override
default void translate(Vector2D<?, ?> vector) {
translate(vector.ix(), vector.iy());
}
/** Translate the shape.
*
* @param dx x translation.
* @param dy y translation.
*/
void translate(int dx, int dy);
@Pure
@Override
default B toBoundingBox() {
final B box = getGeomFactory().newBox();
toBoundingBox(box);
return box;
}
@Pure
@Unefficient
@Override
default boolean intersects(Shape2D<?, ?, ?, ?, ?, ?> shape) {
if (shape instanceof Circle2ai) {
return intersects((Circle2ai<?, ?, ?, ?, ?, ?>) shape);
}
if (shape instanceof Path2ai) {
return intersects((Path2ai<?, ?, ?, ?, ?, ?>) shape);
}
if (shape instanceof PathIterator2ai) {
return intersects((PathIterator2ai<?>) shape);
}
if (shape instanceof Rectangle2ai) {
return intersects((Rectangle2ai<?, ?, ?, ?, ?, ?>) shape);
}
if (shape instanceof Segment2ai) {
return intersects((Segment2ai<?, ?, ?, ?, ?, ?>) shape);
}
return intersects(getPathIterator());
}
/** Replies if this shape is intersecting the given rectangle.
*
* @param rectangle the rectangle.
* @return <code>true</code> if this shape is intersecting the given shape;
* <code>false</code> if there is no intersection.
*/
@Pure
boolean intersects(Rectangle2ai<?, ?, ?, ?, ?, ?> rectangle);
/** Replies if this shape is intersecting the given circle.
*
* @param circle the circle.
* @return <code>true</code> if this shape is intersecting the given shape;
* <code>false</code> if there is no intersection.
*/
@Pure
boolean intersects(Circle2ai<?, ?, ?, ?, ?, ?> circle);
/** Replies if this shape is intersecting the given segment.
*
* @param segment the segment.
* @return <code>true</code> if this shape is intersecting the given shape;
* <code>false</code> if there is no intersection.
*/
@Pure
boolean intersects(Segment2ai<?, ?, ?, ?, ?, ?> segment);
/** Replies if this shape is intersecting the given multishape.
*
* @param multishape the multishape.
* @return <code>true</code> if this shape is intersecting the given shape;
* <code>false</code> if there is no intersection.
*/
@Pure
boolean intersects(MultiShape2ai<?, ?, ?, ?, ?, ?, ?> multishape);
/** Replies if this shape is intersecting the given path.
*
* @param path the path.
* @return <code>true</code> if this shape is intersecting the given shape;
* <code>false</code> if there is no intersection.
*/
@Pure
default boolean intersects(Path2ai<?, ?, ?, ?, ?, ?> path) {
return intersects(path.getPathIterator());
}
/** Replies if this shape is intersecting the path described by the given iterator.
*
* @param iterator the path iterator.
* @return <code>true</code> if this shape is intersecting the given shape;
* <code>false</code> if there is no intersection.
*/
@Pure
boolean intersects(PathIterator2ai<?> iterator);
@Pure
@Unefficient
@Override
default double getDistanceSquared(Shape2D<?, ?, ?, ?, ?, ?> shape) {
if (shape instanceof Circle2ai) {
return getDistanceSquared((Circle2ai<?, ?, ?, ?, ?, ?>) shape);
}
if (shape instanceof Path2ai) {
return getDistanceSquared((Path2ai<?, ?, ?, ?, ?, ?>) shape);
}
if (shape instanceof Rectangle2ai) {
return getDistanceSquared((Rectangle2ai<?, ?, ?, ?, ?, ?>) shape);
}
if (shape instanceof Segment2ai) {
return getDistanceSquared((Segment2ai<?, ?, ?, ?, ?, ?>) shape);
}
throw new IllegalArgumentException();
}
/** Replies the minimum distance between this shape and the given rectangle.
*
* @param rectangle the rectangle.
* @return the minimum distance between the two shapes.
*/
@Pure
default double getDistanceSquared(Rectangle2ai<?, ?, ?, ?, ?, ?> rectangle) {
assert rectangle != null : AssertMessages.notNullParameter();
return rectangle.getDistanceSquared(getClosestPointTo(rectangle));
}
/** Replies the minimum distance between this shape and the given circle.
*
* @param circle the circle
* @return the minimum distance between the two shapes.
*/
@Pure
default double getDistanceSquared(Circle2ai<?, ?, ?, ?, ?, ?> circle) {
assert circle != null : AssertMessages.notNullParameter();
return circle.getDistanceSquared(getClosestPointTo(circle));
}
/** Replies the minimum distance between this shape and the given segment.
*
* @param segment the segment.
* @return the minimum distance between the two shapes.
*/
@Pure
default double getDistanceSquared(Segment2ai<?, ?, ?, ?, ?, ?> segment) {
assert segment != null : AssertMessages.notNullParameter();
return segment.getDistanceSquared(getClosestPointTo(segment));
}
/** Replies the minimum distance between this shape and the given multishape.
*
* @param multishape the multishape.
* @return the minimum distance between the two shapes.
*/
@Pure
default double getDistanceSquared(MultiShape2ai<?, ?, ?, ?, ?, ?, ?> multishape) {
assert multishape != null : AssertMessages.notNullParameter();
double minDist = Double.POSITIVE_INFINITY;
double dist;
for (final Shape2ai<?, ?, ?, ?, ?, ?> shape : multishape) {
dist = getDistanceSquared(shape);
if (dist < minDist) {
minDist = dist;
}
}
return minDist;
}
/** Replies the minimum distance between this shape and the given path.
*
* @param path the path.
* @return the minimum distance between the two shapes.
*/
@Pure
default double getDistanceSquared(Path2ai<?, ?, ?, ?, ?, ?> path) {
assert path != null : AssertMessages.notNullParameter();
return path.getDistanceSquared(getClosestPointTo(path));
}
@Pure
@Unefficient
@Override
default P getClosestPointTo(Shape2D<?, ?, ?, ?, ?, ?> shape) {
if (shape instanceof Circle2ai) {
return getClosestPointTo((Circle2ai<?, ?, ?, ?, ?, ?>) shape);
}
if (shape instanceof MultiShape2ai) {
return getClosestPointTo((MultiShape2ai<?, ?, ?, ?, ?, ?, ?>) shape);
}
if (shape instanceof Path2ai) {
return getClosestPointTo((Path2ai<?, ?, ?, ?, ?, ?>) shape);
}
if (shape instanceof Rectangle2ai) {
return getClosestPointTo((Rectangle2ai<?, ?, ?, ?, ?, ?>) shape);
}
if (shape instanceof Segment2ai) {
return getClosestPointTo((Segment2ai<?, ?, ?, ?, ?, ?>) shape);
}
throw new IllegalArgumentException();
}
/** Replies the closest point on this shape to the given rectangle.
*
* @param rectangle the rectangle.
* @return the closest point on this shape to the given shape; or the point
* if the point is in this shape.
*/
@Pure
P getClosestPointTo(Rectangle2ai<?, ?, ?, ?, ?, ?> rectangle);
/** Replies the closest point on this shape to the given rectangle.
*
* @param circle the circle.
* @return the closest point on this shape to the given shape; or the point
* if the point is in this shape.
*/
@Pure
P getClosestPointTo(Circle2ai<?, ?, ?, ?, ?, ?> circle);
/** Replies the closest point on this shape to the given rectangle.
*
* @param segment the segment.
* @return the closest point on this shape to the given shape; or the point
* if the point is in this shape.
*/
@Pure
P getClosestPointTo(Segment2ai<?, ?, ?, ?, ?, ?> segment);
/** Replies the closest point on this shape to the given rectangle.
*
* @param multishape the multishape.
* @return the closest point on this shape to the given shape; or the point
* if the point is in this shape.
*/
@Pure
default P getClosestPointTo(MultiShape2ai<?, ?, ?, ?, ?, ?, ?> multishape) {
assert multishape != null : AssertMessages.notNullParameter();
Shape2ai<?, ?, ?, ?, ?, ?> closest = null;
double minDist = Double.POSITIVE_INFINITY;
double dist;
for (final Shape2ai<?, ?, ?, ?, ?, ?> shape : multishape) {
dist = getDistanceSquared(shape);
if (dist < minDist) {
minDist = dist;
closest = shape;
}
}
if (closest == null) {
return getGeomFactory().newPoint();
}
return getClosestPointTo(closest);
}
/** Replies the closest point on this shape to the given rectangle.
*
* @param path the path.
* @return the closest point on this shape to the given shape; or the point
* if the point is in this shape.
*/
@Pure
P getClosestPointTo(Path2ai<?, ?, ?, ?, ?, ?> path);
@Override
GeomFactory2ai<IE, P, V, B> getGeomFactory();
@Pure
@SuppressWarnings("unchecked")
@Override
default ST createTransformedShape(Transform2D transform) {
if (transform == null || transform.isIdentity()) {
return (ST) clone();
}
final PathIterator2ai<?> pi = getPathIterator(transform);
final GeomFactory2ai<IE, P, V, B> factory = getGeomFactory();
final Path2ai<?, ?, ?, P, V, ?> newPath = factory.newPath(pi.getWindingRule());
while (pi.hasNext()) {
final PathElement2ai e = pi.next();
switch (e.getType()) {
case MOVE_TO:
newPath.moveTo(e.getToX(), e.getToY());
break;
case LINE_TO:
newPath.lineTo(e.getToX(), e.getToY());
break;
case QUAD_TO:
newPath.quadTo(e.getCtrlX1(), e.getCtrlY1(), e.getToX(), e.getToY());
break;
case CURVE_TO:
newPath.curveTo(e.getCtrlX1(), e.getCtrlY1(), e.getCtrlX2(), e.getCtrlY2(), e.getToX(), e.getToY());
break;
case ARC_TO:
newPath.arcTo(e.getToX(), e.getToY(), e.getRadiusX(), e.getRadiusY(), e.getRotationX(),
e.getLargeArcFlag(), e.getSweepFlag());
break;
case CLOSE:
newPath.closePath();
break;
default:
}
}
return (ST) newPath;
}
}