/*
* $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.d3.afp;
import java.util.NoSuchElementException;
import org.eclipse.xtext.xbase.lib.Pure;
import org.arakhne.afc.math.MathConstants;
import org.arakhne.afc.math.MathUtil;
import org.arakhne.afc.math.geometry.CrossingComputationType;
import org.arakhne.afc.math.geometry.PathWindingRule;
import org.arakhne.afc.math.geometry.d3.Point3D;
import org.arakhne.afc.math.geometry.d3.Transform3D;
import org.arakhne.afc.math.geometry.d3.Vector3D;
import org.arakhne.afc.vmutil.asserts.AssertMessages;
/** Fonctional interface that represented a 2D sphere on a plane.
*
* @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$
* @author $Author: hjaffali$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
public interface Sphere3afp<
ST extends Shape3afp<?, ?, IE, P, V, B>,
IT extends Sphere3afp<?, ?, IE, P, V, B>,
IE extends PathElement3afp,
P extends Point3D<? super P, ? super V>,
V extends Vector3D<? super V, ? super P>,
B extends RectangularPrism3afp<?, ?, IE, P, V, B>>
extends Prism3afp<ST, IT, IE, P, V, B> {
/**
* Replies if the given point is inside the given ellipse.
*
* @param px is the point to test.
* @param py is the point to test.
* @param pz is the point to test.
* @param cx is the center of the sphere.
* @param cy is the center of the sphere.
* @param cz is the center of the sphere.
* @param radius is the radius of the sphere.
* @return <code>true</code> if the point is inside the sphere;
* <code>false</code> if not.
*/
@Pure
static boolean containsSpherePoint(double cx, double cy, double cz, double radius, double px, double py, double pz) {
assert radius >= 0 : AssertMessages.positiveOrZeroParameter(3);
return Point3D.getDistanceSquaredPointPoint(
px, py, pz,
cx, cy, cz) <= (radius * radius);
}
/** Replies if a rectangular prism is inside in the sphere.
*
* @param cx is the center of the sphere.
* @param cy is the center of the sphere.
* @param cz is the center of the sphere.
* @param radius is the radius of the sphere.
* @param rxmin is the lowest corner of the rectangle.
* @param rymin is the lowest corner of the rectangle.
* @param rzmin is the lowest corner of the rectangle.
* @param rxmax is the uppest corner of the rectangle.
* @param rymax is the uppest corner of the rectangle.
* @param rzmax is the uppest corner of the rectangle.
* @return <code>true</code> if the given rectangle is inside the sphere;
* otherwise <code>false</code>.
*/
@Pure
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:magicnumber"})
static boolean containsSphereRectangularPrism(double cx, double cy, double cz, double radius, double rxmin, double rymin,
double rzmin, double rxmax, double rymax, double rzmax) {
assert radius >= 0 : AssertMessages.positiveOrZeroParameter(3);
assert rxmin <= rxmax : AssertMessages.lowerEqualParameters(4, rxmin, 7, rxmax);
assert rymin <= rymax : AssertMessages.lowerEqualParameters(5, rymin, 8, rymax);
assert rzmin <= rzmax : AssertMessages.lowerEqualParameters(6, rzmin, 9, rzmax);
final double rcx = (rxmin + rxmax) / 2;
final double rcy = (rymin + rymax) / 2;
final double rcz = (rzmin + rzmax) / 2;
final double farX;
if (cx <= rcx) {
farX = rxmax;
} else {
farX = rxmin;
}
final double farY;
if (cy <= rcy) {
farY = rymax;
} else {
farY = rymin;
}
final double farZ;
if (cz <= rcz) {
farZ = rzmax;
} else {
farZ = rzmin;
}
return containsSpherePoint(cx, cy, cz, radius, farX, farY, farZ);
}
/** Replies if two spheres are intersecting.
*
* @param x1 is the center of the first sphere
* @param y1 is the center of the first sphere
* @param z1 is the center of the first sphere
* @param radius1 is the radius of the first sphere
* @param x2 is the center of the second sphere
* @param y2 is the center of the second sphere
* @param z2 is the center of the second sphere
* @param radius2 is the radius of the second sphere
* @return <code>true</code> if the two shapes are intersecting; otherwise
* <code>false</code>
*/
@Pure
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:magicnumber"})
static boolean intersectsSphereSphere(double x1, double y1, double z1, double radius1, double x2, double y2, double z2,
double radius2) {
assert radius1 >= 0 : AssertMessages.positiveOrZeroParameter(3);
assert radius2 >= 0 : AssertMessages.positiveOrZeroParameter(7);
final double r = radius1 + radius2;
return Point3D.getDistanceSquaredPointPoint(x1, y1, z1, x2, y2, z2) < (r * r);
}
/** Replies if a sphere and a rectangle are intersecting.
*
* @param x1 is the center of the sphere
* @param y1 is the center of the sphere
* @param z1 is the center of the sphere
* @param radius is the radius of the sphere
* @param x2 is the first corner of the rectangle.
* @param y2 is the first corner of the rectangle.
* @param z2 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.
* @param z3 is the second corner of the rectangle.
* @return <code>true</code> if the two shapes are intersecting; otherwise
* <code>false</code>
*/
@Pure
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:magicnumber"})
static boolean intersectsSpherePrism(double x1, double y1, double z1, double radius, double x2, double y2, double z2,
double x3, double y3, double z3) {
assert radius >= 0 : AssertMessages.positiveOrZeroParameter(3);
assert x2 <= x3 : AssertMessages.lowerEqualParameters(4, x2, 7, x3);
assert y2 <= y3 : AssertMessages.lowerEqualParameters(5, y2, 7, y3);
assert z2 <= z3 : AssertMessages.lowerEqualParameters(6, z2, 7, z3);
final double dx;
if (x1 < x2) {
dx = x2 - x1;
} else if (x1 > x3) {
dx = x1 - x3;
} else {
dx = 0;
}
final double dy;
if (y1 < y2) {
dy = y2 - y1;
} else if (y1 > y3) {
dy = y1 - y3;
} else {
dy = 0;
}
final double dz;
if (z1 < z2) {
dz = z2 - z1;
} else if (z1 > z3) {
dz = z1 - z3;
} else {
dz = 0;
}
return (dx * dx + dy * dy + dz * dz) < (radius * radius);
}
/** Replies if a sphere and a line are intersecting.
*
* @param x1 is the center of the sphere
* @param y1 is the center of the sphere
* @param z1 is the center of the sphere
* @param radius is the radius of the sphere
* @param x2 is the first point of the line.
* @param y2 is the first point of the line.
* @param z2 is the first point of the line.
* @param x3 is the second point of the line.
* @param y3 is the second point of the line.
* @param z3 is the second point of the line.
* @return <code>true</code> if the two shapes are intersecting; otherwise
* <code>false</code>
*/
@Pure
@SuppressWarnings("checkstyle:parameternumber")
static boolean intersectsSphereLine(double x1, double y1, double z1, double radius, double x2, double y2, double z2,
double x3, double y3, double z3) {
assert radius >= 0 : AssertMessages.positiveOrZeroParameter(3);
return Segment3afp.computeDistanceSquaredLinePoint(x2, y2, z2, x3, y3, z3, x1, y1, z1) < (radius * radius);
}
/** Replies if a sphere and a segment are intersecting.
*
* @param x1 is the center of the sphere
* @param y1 is the center of the sphere
* @param z1 is the center of the sphere
* @param radius is the radius of the sphere
* @param x2 is the first point of the segment.
* @param y2 is the first point of the segment.
* @param z2 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.
* @param z3 is the second point of the segment.
* @return <code>true</code> if the two shapes are intersecting; otherwise
* <code>false</code>
*/
@Pure
@SuppressWarnings("checkstyle:parameternumber")
static boolean intersectsSphereSegment(double x1, double y1, double z1, double radius, double x2, double y2, double z2,
double x3, double y3, double z3) {
assert radius >= 0 : AssertMessages.positiveOrZeroParameter(3);
return Segment3afp.computeDistanceSquaredSegmentPoint(x2, y2, z2, x3, y3, z3, x1, y1, z1) < (radius * radius);
}
@Pure
@Override
default boolean equalsToShape(IT shape) {
if (shape == null) {
return false;
}
if (shape == this) {
return true;
}
return getX() == shape.getX()
&& getY() == shape.getY()
&& getZ() == shape.getZ()
&& getRadius() == shape.getRadius();
}
/** Replies the center X.
*
* @return the center x.
*/
@Pure
double getX();
/** Replies the center y.
*
* @return the center y.
*/
@Pure
double getY();
/** Replies the center z.
*
* @return the center z.
*/
@Pure
double getZ();
/** Replies the center.
*
* @return a copy of the center.
*/
@Override
@Pure
default P getCenter() {
return getGeomFactory().newPoint(getX(), getY(), getZ());
}
/** Change the center.
*
* @param center the center point.
*/
@Override
default void setCenter(Point3D<?, ?> center) {
assert center != null : AssertMessages.notNullParameter();
set(center.getX(), center.getY(), center.getZ(), getRadius());
}
/** Change the center.
*
* @param x x coordinate of the center point.
* @param y y coordinate of the center point.
* @param z z coordinate of the center point.
*/
@Override
default void setCenter(double x, double y, double z) {
setX(x);
setY(y);
setZ(z);
}
/** Change the x coordinate of the center.
*
* @param x x coordinate of the center point.
*/
void setX(double x);
/** Change the y coordinate of the center.
*
* @param y y coordinate of the center point.
*/
void setY(double y);
/** Change the z coordinate of the center.
*
* @param z z coordinate of the center point.
*/
void setZ(double z);
/** Replies the radius.
*
* @return the radius.
*/
@Pure
double getRadius();
/** Set the radius.
*
* @param radius is the radius.
*/
void setRadius(double radius);
/** Change the frame of the sphere.
*
* @param x x coordinate of the center point.
* @param y y coordinate of the center point.
* @param z z coordinate of the center point.
* @param radius the radius.
*/
// Not a default implementation for ensuring atomic change.
void set(double x, double y, double z, double radius);
/** Change the frame of the sphere.
*
* @param center the center point.
* @param radius the radius.
*/
default void set(Point3D<?, ?> center, double radius) {
assert center != null : AssertMessages.notNullParameter();
set(center.getX(), center.getY(), center.getZ(), radius);
}
@Override
default void set(IT shape) {
assert shape != null : AssertMessages.notNullParameter();
set(shape.getX(), shape.getY(), shape.getZ(), shape.getRadius());
}
@Override
default void clear() {
set(0, 0, 0, 0);
}
@Override
default void toBoundingBox(B box) {
assert box != null : AssertMessages.notNullParameter();
final double x = getX();
final double y = getY();
final double z = getZ();
final double radius = getRadius();
box.setFromCorners(
x - radius, y - radius, z - radius,
x + radius, y + radius, z + radius);
}
@Override
default boolean isEmpty() {
return MathUtil.isEpsilonZero(getRadius());
}
@Pure
@Override
default double getDistance(Point3D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
double distance = Point3D.getDistancePointPoint(getX(), getY(), getZ(), pt.getX(), pt.getY(), pt.getZ());
distance = distance - getRadius();
return Math.max(0., distance);
}
@Pure
@Override
default double getDistanceSquared(Point3D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final double x = getX();
final double y = getY();
final double z = getZ();
final double radius = getRadius();
final double vx = pt.getX() - x;
final double vy = pt.getY() - y;
final double vz = pt.getZ() - z;
final double sqLength = vx * vx + vy * vy + vz * vz;
final double sqRadius = radius * radius;
if (sqLength <= sqRadius) {
return 0;
}
return Math.max(0., sqLength - 2 * Math.sqrt(sqLength) * radius + sqRadius);
}
@Pure
@Override
default double getDistanceL1(Point3D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final Point3D<?, ?> r = getClosestPointTo(pt);
return r.getDistanceL1(pt);
}
@Pure
@Override
default double getDistanceLinf(Point3D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final Point3D<?, ?> r = getClosestPointTo(pt);
return r.getDistanceLinf(pt);
}
@Pure
@Override
default boolean contains(double x, double y, double z) {
return containsSpherePoint(getX(), getY(), getZ(), getRadius(), x, y, z);
}
@Override
default boolean contains(RectangularPrism3afp<?, ?, ?, ?, ?, ?> rectangularPrism) {
assert rectangularPrism != null : AssertMessages.notNullParameter();
return containsSphereRectangularPrism(getX(), getY(), getZ(), getRadius(), rectangularPrism.getMinX(),
rectangularPrism.getMinY(), rectangularPrism.getMinZ(), rectangularPrism.getMaxX(), rectangularPrism.getMaxY(),
rectangularPrism.getMaxZ());
}
@Override
default void translate(double dx, double dy, double dz) {
setCenter(getX() + dx, getY() + dy, getZ() + dz);
}
@Pure
@Override
default boolean intersects(RectangularPrism3afp<?, ?, ?, ?, ?, ?> prism) {
assert prism != null : AssertMessages.notNullParameter();
return intersectsSpherePrism(
getX(), getY(), getZ(), getRadius(),
prism.getMinX(), prism.getMinY(), prism.getMinZ(), prism.getMaxX(), prism.getMaxY(), prism.getMaxZ());
}
@Pure
@Override
default boolean intersects(Sphere3afp<?, ?, ?, ?, ?, ?> sphere) {
assert sphere != null : AssertMessages.notNullParameter();
return intersectsSphereSphere(
getX(), getY(), getZ(), getRadius(),
sphere.getX(), sphere.getY(), sphere.getZ(), sphere.getRadius());
}
@Pure
@Override
default boolean intersects(Segment3afp<?, ?, ?, ?, ?, ?> segment) {
assert segment != null : AssertMessages.notNullParameter();
return intersectsSphereSegment(
getX(), getY(), getZ(), getRadius(),
segment.getX1(), segment.getY1(), segment.getZ1(),
segment.getX2(), segment.getY2(), segment.getZ2());
}
@Pure
@Override
default boolean intersects(PathIterator3afp<?> iterator) {
assert iterator != null : AssertMessages.notNullParameter();
final int mask = iterator.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = Path3afp.computeCrossingsFromSphere(
0,
iterator,
getX(), getY(), getZ(), getRadius(),
CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0;
}
@Pure
@Override
default boolean intersects(MultiShape3afp<?, ?, ?, ?, ?, ?, ?> multishape) {
assert multishape != null : AssertMessages.notNullParameter();
return multishape.intersects(this);
}
@Pure
@Override
default P getClosestPointTo(Point3D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final double x = getX();
final double y = getY();
final double z = getZ();
final double radius = getRadius();
final double vx = pt.getX() - x;
final double vy = pt.getY() - y;
final double vz = pt.getZ() - z;
final double sqLength = vx * vx + vy * vy + vz * vz;
if (sqLength <= (radius * radius)) {
return getGeomFactory().convertToPoint(pt);
}
final double s = radius / Math.sqrt(sqLength);
return getGeomFactory().newPoint(x + vx * s, y + vy * s, z + vz * s);
}
@Pure
@Override
default P getClosestPointTo(Sphere3afp<?, ?, ?, ?, ?, ?> sphere) {
assert sphere != null : AssertMessages.notNullParameter();
final Point3D<?, ?> point = sphere.getClosestPointTo(getCenter());
return getClosestPointTo(point);
}
@Pure
@Override
default P getClosestPointTo(RectangularPrism3afp<?, ?, ?, ?, ?, ?> rectangularPrism) {
assert rectangularPrism != null : AssertMessages.notNullParameter();
final Point3D<?, ?> point = rectangularPrism.getClosestPointTo(getCenter());
return getClosestPointTo(point);
}
@Pure
@Override
default P getClosestPointTo(Segment3afp<?, ?, ?, ?, ?, ?> segment) {
assert segment != null : AssertMessages.notNullParameter();
final Point3D<?, ?> point = segment.getClosestPointTo(getCenter());
return getClosestPointTo(point);
}
@Pure
@Override
default P getClosestPointTo(Path3afp<?, ?, ?, ?, ?, ?> path) {
assert path != null : AssertMessages.notNullParameter();
final Point3D<?, ?> point = path.getClosestPointTo(getCenter());
return getClosestPointTo(point);
}
@Pure
@Override
default P getClosestPointTo(MultiShape3afp<?, ?, ?, ?, ?, ?, ?> multishape) {
assert multishape != null : AssertMessages.notNullParameter();
final Point3D<?, ?> point = multishape.getClosestPointTo(getCenter());
return getClosestPointTo(point);
}
@Pure
@Override
default P getFarthestPointTo(Point3D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final double x = getX();
final double y = getY();
final double z = getZ();
final double vx = x - pt.getX();
final double vy = y - pt.getY();
final double vz = z - pt.getZ();
final double radius = getRadius();
final double sqLength = vx * vx + vy * vy + vz * vz;
if (sqLength <= 0.) {
return getGeomFactory().newPoint(radius, 0, 0);
}
final double s = radius / Math.sqrt(sqLength);
return getGeomFactory().newPoint(x + vx * s, y + vy * s, z + vz * s);
}
@Pure
@Override
default PathIterator3afp<IE> getPathIterator(Transform3D transform) {
if (transform == null || transform.isIdentity()) {
return new SpherePathIterator<>(this);
}
return new TransformedCirclePathIterator<>(this, transform);
}
@Override
@Pure
default double getHeight() {
return getRadius();
}
@Override
@Pure
default double getDepth() {
return getRadius();
}
@Override
@Pure
default double getWidth() {
return getRadius();
}
/** {@inheritDoc}
*
* <p>The sphere is set in order to be enclosed inside the given box.
* It means that the center of the sphere is the center of the box, and the
* radius of the sphere is the minimum of the demi-width, demi-height and demi-depth.
*/
@Override
default void setFromCenter(double centerX, double centerY, double centerZ, double cornerX, double cornerY, double cornerZ) {
final double demiWidth = Math.abs(cornerX - centerX);
final double demiHeight = Math.abs(cornerY - centerY);
final double demiDepth = Math.abs(cornerZ - centerZ);
set(centerX, centerY, centerZ, MathUtil.min(demiHeight, demiWidth, demiDepth));
}
/** {@inheritDoc}
*
* <p>The sphere is set in order to be enclosed inside the given box.
* It means that the center of the sphere is the center of the box, and the
* radius of the sphere is the minimum of the demi-width and demi-height.
*/
@Override
default void setFromCorners(double x1, double y1, double z1, double x2, double y2, double z2) {
setFromCenter((x1 + x2) / 2., (y1 + y2) / 2., (z1 + z2) / 2., x2, y2, z2);
}
@Override
default double getMinX() {
return getX() - getRadius();
}
/** {@inheritDoc}
*
* <p>Assuming that the maximum X coordinate should not change, the center of
* the sphere is the point between the new minimum and the current maximum coordinates,
* and the radius of the sphere is set to the difference between the new minimum and
* center coordinates.
*
* <p>If the new minimum is greater than the current maximum, the coordinates are automatically
* swapped.
*/
@Override
default void setMinX(double x) {
final double cx = (x + getX() + getRadius()) / 2.;
final double radius = Math.abs(cx - x);
set(cx, getY(), getZ(), radius);
}
@Override
default double getMaxX() {
return getX() + getRadius();
}
/** {@inheritDoc}
*
* <p>Assuming that the minimum X coordinate should not change, the center of
* the sphere is the point between the new maximum and the current minimum coordinates,
* and the radius of the sphere is set to the difference between the new maximum and
* center coordinates.
*
* <p>If the new maximum is lower than the current minimum, the coordinates are automatically
* swapped.
*/
@Override
default void setMaxX(double x) {
final double cx = (x + getX() - getRadius()) / 2.;
final double radius = Math.abs(cx - x);
set(cx, getY(), getZ(), radius);
}
@Override
default double getMinY() {
return getY() - getRadius();
}
/** {@inheritDoc}
*
* <p>Assuming that the maximum Y coordinate should not change, the center of
* the sphere is the point between the new minimum and the current maximum coordinates,
* and the radius of the sphere is set to the difference between the new minimum and
* center coordinates.
*
* <p>If the new minimum is greater than the current maximum, the coordinates are automatically
* swapped.
*/
@Override
default void setMinY(double y) {
final double cy = (y + getY() + getRadius()) / 2.;
final double radius = Math.abs(cy - y);
set(getX(), cy, getZ(), radius);
}
@Override
default double getMaxY() {
return getY() + getRadius();
}
/** {@inheritDoc}
*
* <p>Assuming that the minimum Y coordinate should not change, the center of
* the sphere is the point between the new maximum and the current minimum coordinates,
* and the radius of the sphere is set to the difference between the new maximum and
* center coordinates.
*
* <p>If the new maximum is lower than the current minimum, the coordinates are automatically
* swapped.
*/
@Override
default void setMaxY(double y) {
final double cy = (y + getY() - getRadius()) / 2.;
final double radius = Math.abs(cy - y);
set(getX(), cy, getZ(), radius);
}
@Override
default double getMinZ() {
return getZ() - getRadius();
}
/** {@inheritDoc}
*
* <p>Assuming that the maximum Z coordinate should not change, the center of
* the sphere is the point between the new minimum and the current maximum coordinates,
* and the radius of the sphere is set to the difference between the new minimum and
* center coordinates.
*
* <p>If the new minimum is greater than the current maximum, the coordinates are automatically
* swapped.
*/
@Override
default void setMinZ(double z) {
final double cz = (z + getZ() + getRadius()) / 2.;
final double radius = Math.abs(cz - z);
set(getX(), getY(), cz, radius);
}
@Override
default double getMaxZ() {
return getZ() + getRadius();
}
/** {@inheritDoc}
*
* <p>Assuming that the minimum Z coordinate should not change, the center of
* the sphere is the point between the new maximum and the current minimum coordinates,
* and the radius of the sphere is set to the difference between the new maximum and
* center coordinates.
*
* <p>If the new maximum is lower than the current minimum, the coordinates are automatically
* swapped.
*/
@Override
default void setMaxZ(double z) {
final double cz = (z + getZ() - getRadius()) / 2.;
final double radius = Math.abs(cz - z);
set(getX(), getY(), cz, radius);
}
/** Abstract iterator on the path elements of the sphere.
*
* <h3>Discretization of the sphere with Bezier</h3>
*
* <p>For n segments on the sphere, the optimal distance to the control points, in the sense that the
* middle of the curve lies on the sphere itself, is (4/3)*tan(pi/(2n)).
*
* <p><img src="../../d2/afp/doc-files/circlebezier.png" width="100%" />
*
* <p>In the case of a discretization with 4 bezier curves, the distance is is
* (4/3)*tan(pi/8) = 4*(sqrt(2)-1)/3 = 0.552284749831.
*
* <p><img src="../../d2/afp/doc-files/circlepathiterator.png" width="100%" />
*
* @param <T> the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
abstract class AbstractSpherePathIterator<T extends PathElement3afp> implements PathIterator3afp<T> {
/**
* Distance from a Bezier curve control point on the sphere to the other control point.
*
* <p>4/3 tan (PI/(2*n)), where n is the number on points on the sphere.
*/
public static final double CTRL_POINT_DISTANCE = 0.5522847498307933;
/**
* Contains the control points for a set of 4 cubic
* bezier curves that approximate a sphere of radius 1
* centered at (0,0,0).
*/
// TODO : change from 2D to 3D
public static final double[][] BEZIER_CONTROL_POINTS = {
// First quarter: max x, max y.
{1, CTRL_POINT_DISTANCE, CTRL_POINT_DISTANCE, 1, 0, 1 },
// Second quarter: min x, max y.
{-CTRL_POINT_DISTANCE, 1, -1, CTRL_POINT_DISTANCE, -1, 0 },
// Third quarter: min x, min y.
{-1, -CTRL_POINT_DISTANCE, -CTRL_POINT_DISTANCE, -1, 0, -1 },
// Fourth quarter: max x, min y.
{CTRL_POINT_DISTANCE, -1, 1, -CTRL_POINT_DISTANCE, 1, 0 },
};
/** 4 segments + close.
*/
protected static final int NUMBER_ELEMENTS = 5;
/** The iterated shape.
*/
protected final Sphere3afp<?, ?, T, ?, ?, ?> sphere;
/**
* @param sphere the sphere.
*/
public AbstractSpherePathIterator(Sphere3afp<?, ?, T, ?, ?, ?> sphere) {
assert sphere != null : AssertMessages.notNullParameter();
this.sphere = sphere;
}
@Override
public GeomFactory3afp<T, ?, ?, ?> getGeomFactory() {
return this.sphere.getGeomFactory();
}
@Pure
@Override
public PathWindingRule getWindingRule() {
return PathWindingRule.NON_ZERO;
}
@Pure
@Override
public boolean isPolyline() {
return false;
}
@Override
public boolean isCurved() {
return true;
}
@Override
public boolean isPolygon() {
return true;
}
@Pure
@Override
public boolean isMultiParts() {
return false;
}
}
/** Iterator on the path elements of the sphere.
*
* @param <T> the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
@SuppressWarnings("checkstyle:magicnumber")
class SpherePathIterator<T extends PathElement3afp> extends AbstractSpherePathIterator<T> {
private double x;
private double y;
private double z;
private double radius;
private int index;
private double movex;
private double movey;
private double movez;
private double lastx;
private double lasty;
private double lastz;
/**
* @param sphere the sphere to iterate on.
*/
public SpherePathIterator(Sphere3afp<?, ?, T, ?, ?, ?> sphere) {
super(sphere);
if (sphere.isEmpty()) {
this.index = NUMBER_ELEMENTS;
} else {
this.radius = sphere.getRadius();
this.x = sphere.getX();
this.y = sphere.getY();
this.z = sphere.getZ();
this.index = -1;
}
}
@Override
public PathIterator3afp<T> restartIterations() {
return new SpherePathIterator<>(this.sphere);
}
@Pure
@Override
public boolean hasNext() {
return this.index < NUMBER_ELEMENTS;
}
@Override
public T next() {
if (this.index >= NUMBER_ELEMENTS) {
throw new NoSuchElementException();
}
final int idx = this.index;
++this.index;
if (idx < 0) {
final double[] ctrls = BEZIER_CONTROL_POINTS[3];
this.movex = this.x + ctrls[4] * this.radius;
this.movey = this.y + ctrls[5] * this.radius;
this.movez = this.z + ctrls[6] * this.radius;
this.lastx = this.movex;
this.lasty = this.movey;
this.lastz = this.movez;
return getGeomFactory().newMovePathElement(
this.lastx, this.lasty, this.lastz);
}
if (idx < (NUMBER_ELEMENTS - 1)) {
final double[] ctrls = BEZIER_CONTROL_POINTS[idx];
final double ppx = this.lastx;
final double ppy = this.lasty;
final double ppz = this.lastz;
this.lastx = this.x + ctrls[4] * this.radius;
this.lasty = this.y + ctrls[5] * this.radius;
this.lastz = this.z + ctrls[6] * this.radius;
return getGeomFactory().newCurvePathElement(
ppx, ppy, ppz,
this.x + ctrls[0] * this.radius,
this.y + ctrls[1] * this.radius,
this.z + ctrls[2] * this.radius,
this.x + ctrls[3] * this.radius,
this.y + ctrls[4] * this.radius,
this.z + ctrls[5] * this.radius,
this.lastx, this.lasty, this.lastz);
}
return getGeomFactory().newClosePathElement(
this.lastx, this.lasty, this.lastz,
this.movex, this.movey, this.movez);
}
}
/** Iterator on the path elements of the sphere.
*
* @param <T> the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
@SuppressWarnings("checkstyle:magicnumber")
class TransformedCirclePathIterator<T extends PathElement3afp> extends AbstractSpherePathIterator<T> {
private final Transform3D transform;
private final Point3D<?, ?> tmpPoint;
private double x;
private double y;
private double z;
private double radius;
private double movex;
private double movey;
private double movez;
private double lastx;
private double lasty;
private double lastz;
private int index;
/**
* @param sphere the iterated sphere.
* @param transform the transformation to apply.
*/
public TransformedCirclePathIterator(Sphere3afp<?, ?, T, ?, ?, ?> sphere, Transform3D transform) {
super(sphere);
assert transform != null : AssertMessages.notNullParameter();
this.transform = transform;
if (sphere.isEmpty()) {
this.index = NUMBER_ELEMENTS;
this.tmpPoint = null;
} else {
this.tmpPoint = new InnerComputationPoint3afp();
this.radius = sphere.getRadius();
this.x = sphere.getX();
this.y = sphere.getY();
this.z = sphere.getZ();
this.index = -1;
}
}
@Override
public PathIterator3afp<T> restartIterations() {
return new TransformedCirclePathIterator<>(this.sphere, this.transform);
}
@Pure
@Override
public boolean hasNext() {
return this.index < NUMBER_ELEMENTS;
}
@Override
public T next() {
if (this.index >= NUMBER_ELEMENTS) {
throw new NoSuchElementException();
}
final int idx = this.index;
++this.index;
if (idx < 0) {
final double[] ctrls = BEZIER_CONTROL_POINTS[3];
this.tmpPoint.set(this.x + ctrls[4] * this.radius, this.y + ctrls[5] * this.radius,
this.z + ctrls[6] * this.radius);
this.transform.transform(this.tmpPoint);
this.movex = this.tmpPoint.getX();
this.lastx = this.movex;
this.movey = this.tmpPoint.getY();
this.lasty = this.movey;
this.movez = this.tmpPoint.getZ();
this.lastz = this.movez;
return getGeomFactory().newMovePathElement(
this.lastx, this.lasty, this.lastz);
}
if (idx < (NUMBER_ELEMENTS - 1)) {
final double[] ctrls = BEZIER_CONTROL_POINTS[idx];
final double ppx = this.lastx;
final double ppy = this.lasty;
final double ppz = this.lastz;
this.tmpPoint.set(this.x + ctrls[0] * this.radius, this.y + ctrls[1] * this.radius,
this.z + ctrls[2] * this.radius);
this.transform.transform(this.tmpPoint);
final double ctrlX1 = this.tmpPoint.getX();
final double ctrlY1 = this.tmpPoint.getY();
final double ctrlZ1 = this.tmpPoint.getZ();
this.tmpPoint.set(this.x + ctrls[3] * this.radius, this.y + ctrls[4] * this.radius,
this.z + ctrls[5] * this.radius);
this.transform.transform(this.tmpPoint);
final double ctrlX2 = this.tmpPoint.getX();
final double ctrlY2 = this.tmpPoint.getY();
final double ctrlZ2 = this.tmpPoint.getZ();
this.tmpPoint.set(this.x + ctrls[6] * this.radius, this.y + ctrls[7] * this.radius,
this.z + ctrls[8] * this.radius);
this.transform.transform(this.tmpPoint);
this.lastx = this.tmpPoint.getX();
this.lasty = this.tmpPoint.getY();
this.lastz = this.tmpPoint.getZ();
return getGeomFactory().newCurvePathElement(
ppx, ppy, ppz,
ctrlX1, ctrlY1, ctrlZ1,
ctrlX2, ctrlY2, ctrlZ2,
this.lastx, this.lasty, this.lastz);
}
return getGeomFactory().newClosePathElement(
this.lastx, this.lasty, this.lastz,
this.movex, this.movey, this.movez);
}
}
}