/*
* $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.ai;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.xtext.xbase.lib.Pure;
import org.arakhne.afc.math.MathConstants;
import org.arakhne.afc.math.geometry.CrossingComputationType;
import org.arakhne.afc.math.geometry.PathWindingRule;
import org.arakhne.afc.math.geometry.d3.GeomFactory3D;
import org.arakhne.afc.math.geometry.d3.Point3D;
import org.arakhne.afc.math.geometry.d3.Transform3D;
import org.arakhne.afc.math.geometry.d3.Tuple3iComparator;
import org.arakhne.afc.math.geometry.d3.Vector3D;
import org.arakhne.afc.vmutil.asserts.AssertMessages;
/** Fonctional interface that represented a 3D sphere.
*
* @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 Sphere3ai<
ST extends Shape3ai<?, ?, IE, P, V, B>,
IT extends Sphere3ai<?, ?, IE, P, V, B>,
IE extends PathElement3ai,
P extends Point3D<? super P, ? super V>,
V extends Vector3D<? super V, ? super P>,
B extends RectangularPrism3ai<?, ?, IE, P, V, B>>
extends Shape3ai<ST, IT, IE, P, V, B> {
/** Replies if the given point is inside the sphere.
*
* @param cx is the x-coordinate of the sphere center
* @param cy is the y-coordinate of the sphere center
* @param cz is the z-coordinate of the sphere center
* @param cr is the radius of the sphere center
* @param x is the x-coordinate of the point
* @param y is the y-coordinate of the point
* @param z is the z-coordinate of the point
* @return <code>true</code> if the point is inside the sphere.
*/
@Pure
@SuppressWarnings("checkstyle:magicnumber")
static boolean contains(int cx, int cy, int cz, int cr, int x, int y, int z) {
assert cr >= 0 : AssertMessages.positiveOrZeroParameter(2);
final int vx = x - cx;
final int vy = y - cy;
final int vz = z - cz;
if (vx >= -cr && vx <= cr && vy >= -cr && vy <= cr && vz >= -cr && vz <= cr) {
final int octant;
final boolean xpos = vx >= 0;
final boolean ypos = vy >= 0;
//TODO final boolean zpos = vz >= 0;
if (xpos) {
if (ypos) {
octant = 0;
} else {
octant = 2;
}
} else {
if (ypos) {
octant = 6;
} else {
octant = 4;
}
}
boolean allNull = true;
final SpherePerimeterIterator<InnerComputationPoint3ai, InnerComputationVector3ai> iterator =
new SpherePerimeterIterator<>(
InnerComputationGeomFactory.SINGLETON, cx, cy, cz, cr, octant, octant + 2, false);
while (iterator.hasNext()) {
final Point3D<?, ?> pt = iterator.next();
// Trivial case
if (pt.ix() == x && pt.iy() == y) {
return true;
}
final int px = cy - pt.iy();
final int py = pt.ix() - cx;
final int cpx = x - pt.ix();
final int cpy = y - pt.iy();
final int 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 sphere.
*
* @param cx is the x-coordinate of the sphere center
* @param cy is the y-coordinate of the sphere center
* @param cz is the z-coordinate of the sphere center
* @param cr is the radius of the sphere center
* @param quadrant is the quadrant: <table>
* <thead>
* <th><td>quadrant</td><td>x</td><td>y</td></th>
* </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 x is the x-coordinate of the point
* @param y is the y-coordinate of the point
* @param z is the z-coordinate of the point
* @return <code>true</code> if the point is inside the sphere.
*/
@Pure
@SuppressWarnings("checkstyle:magicnumber")
static boolean contains(int cx, int cy, int cz, int cr, int quadrant, int x, int y, int z) {
assert cr >= 0 : AssertMessages.positiveOrZeroParameter(3);
assert quadrant >= 0 && quadrant <= 3 : AssertMessages.outsideRangeInclusiveParameter(quadrant, 0, 3);
final int vx = x - cx;
final int vy = y - cy;
if (vx >= -cr && vx <= cr && vy >= -cr && vy <= cr) {
final int octant;
final boolean xpos = vx >= 0;
final 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;
}
final SpherePerimeterIterator<InnerComputationPoint3ai, InnerComputationVector3ai> iterator =
new SpherePerimeterIterator<>(InnerComputationGeomFactory.SINGLETON,
cx, cy, cz, cr, octant, octant + 2, false);
while (iterator.hasNext()) {
final Point3D<?, ?> p = iterator.next();
final int px = cy - p.iy();
final int py = p.ix() - cx;
final int cpx = x - p.ix();
final int cpy = y - p.iy();
final int ccw = cpx * py - cpy * px;
if (ccw > 0) {
return false;
}
}
return true;
}
return false;
}
@Pure
@Override
default boolean contains(int x, int y, int z) {
return contains(getX(), getY(), getZ(), getRadius(), x, y, z);
}
// TODO : integrate z coordinate
@Pure
@Override
@SuppressWarnings({"checkstyle:booleanexpressioncomplexity", "checkstyle:magicnumber", "checkstyle:cyclomaticcomplexity"})
default boolean contains(RectangularPrism3ai<?, ?, ?, ?, ?, ?> box) {
assert box != null : AssertMessages.notNullParameter();
final int cx = getX();
final int cy = getY();
final int cz = getZ();
final int radius = getRadius();
final int vx1 = box.getMinX() - cx;
final int vy1 = box.getMinY() - cy;
//TODO final int vz1 = box.getMinZ() - cz;
final int vx2 = box.getMaxX() - cx;
final int vy2 = box.getMaxY() - cy;
//TODO final int vz2 = box.getMaxZ() - cz;
if (vx1 >= -radius && vx1 <= radius && vy1 >= -radius && vy1 <= radius && vx2 >= -radius && vx2 <= radius
&& vy2 >= -radius && vy2 <= radius) {
final int[] quadrants = new int[4];
final int[] x = new int[] {vx1, vx2, vx2, vx1};
final int[] y = new int[] {vy1, vy1, vy2, vy2};
for (int i = 0; i < 4; ++i) {
final int xcoord = x[i];
final int ycoord = y[i];
final int flag = 1 << i;
if (xcoord > 0) {
if (ycoord > 0) {
quadrants[0] |= flag;
} else {
quadrants[1] |= flag;
}
} else {
if (ycoord > 0) {
quadrants[3] |= flag;
} else {
quadrants[2] |= flag;
}
}
}
for (int i = 0; i < quadrants.length; ++i) {
if (quadrants[i] != 0) {
final SpherePerimeterIterator<P, V> iterator = new SpherePerimeterIterator<>(
getGeomFactory(), cx, cy, cz, radius, i * 2, i * 2 + 2, false);
while (iterator.hasNext()) {
final P p = iterator.next();
final int px = cy - p.iy();
final int py = p.ix() - cx;
for (int j = 0; j < 4; ++j) {
if ((quadrants[i] & (1 << j)) != 0) {
final int cpx = x[j] - p.ix();
final int cpy = y[j] - p.iy();
final int ccw = cpx * py - cpy * px;
if (ccw > 0) {
return false;
}
}
}
}
}
}
return true;
}
return false;
}
/** Replies the closest point in a sphere to a point.
*
* <p>The closest point is the point on the perimeter or inside the sphere's disk that
* has the lowest Manhatan distance to the given origin point.
*
* @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 cr is the radius of the sphere
* @param x is the point
* @param y is the point
* @param z is the point
* @param result the closest point in the sphere to the point.
*/
@Pure
@SuppressWarnings("checkstyle:magicnumber")
static void computeClosestPointTo(int cx, int cy, int cz, int cr, int x, int y, int z, Point3D<?, ?> result) {
assert cr >= 0 : AssertMessages.positiveOrZeroParameter(3);
assert result != null : AssertMessages.notNullParameter(7);
final int vx = x - cx;
final int vy = y - cy;
final int octant;
final boolean xpos = vx >= 0;
final boolean ypos = vy >= 0;
if (xpos) {
if (ypos) {
octant = 0;
} else {
octant = 2;
}
} else {
if (ypos) {
octant = 6;
} else {
octant = 4;
}
}
final SpherePerimeterIterator<InnerComputationPoint3ai, InnerComputationVector3ai> iterator =
new SpherePerimeterIterator<>(InnerComputationGeomFactory.SINGLETON, cx, cy, cz, cr, octant, octant + 2, false);
boolean isInside = true;
int minDist = Integer.MAX_VALUE;
while (iterator.hasNext()) {
final Point3D<?, ?> p = iterator.next();
final int px = cy - p.iy();
final int py = p.ix() - cx;
final int cpx = x - p.ix();
final int cpy = y - p.iy();
final int ccw = cpx * py - cpy * px;
if (ccw >= 0) {
isInside = false;
// Mahantan distance
final int dist = Math.abs(cpx) + Math.abs(cpy);
if (dist < minDist) {
minDist = dist;
result.set(p);
}
}
}
// inside the sphere
if (isInside) {
result.set(x, y, z);
}
}
/** Replies the farthest point in a sphere to a point.
*
* <p>The farthest point is the point on the perimeter of the sphere that has the highest Manhatan distance
* to the given origin point.
*
* @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 cr is the radius of the sphere
* @param x is the point
* @param y is the point
* @param z is the point
* @param result the farthest point in the sphere to the point.
*/
@Pure
@SuppressWarnings("checkstyle:magicnumber")
static void computeFarthestPointTo(int cx, int cy, int cz, int cr, int x, int y, int z, Point3D<?, ?> result) {
assert cr >= 0 : AssertMessages.positiveOrZeroParameter(3);
final int vx = x - cx;
final int vy = y - cy;
final int octant;
final boolean xpos = vx >= 0;
final boolean ypos = vy >= 0;
if (xpos) {
if (ypos) {
octant = 4;
} else {
octant = 6;
}
} else {
if (ypos) {
octant = 2;
} else {
octant = 0;
}
}
final SpherePerimeterIterator<InnerComputationPoint3ai, InnerComputationVector3ai> iterator =
new SpherePerimeterIterator<>(InnerComputationGeomFactory.SINGLETON, cx, cy, cz, cr, octant, octant + 2, false);
int maxL1Dist = Integer.MIN_VALUE;
int maxLinfDist = Integer.MIN_VALUE;
result.set(x, y, z);
while (iterator.hasNext()) {
final Point3D<?, ?> p = iterator.next();
final int cpx = Math.abs(p.ix() - x);
final int cpy = Math.abs(p.iy() - y);
// Mahantan distance
final int l1 = cpx + cpy;
final int linfinv = Math.min(cpx, cpy);
if (l1 > maxL1Dist || (l1 == maxL1Dist && linfinv < maxLinfDist)) {
maxL1Dist = l1;
maxLinfDist = linfinv;
result.set(p);
}
}
}
/** 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:magicnumber")
static boolean intersectsSphereSphere(int x1, int y1, int z1, int radius1, int x2, int y2, int z2, int radius2) {
assert radius1 >= 0 : AssertMessages.positiveOrZeroParameter(3);
assert radius2 >= 0 : AssertMessages.positiveOrZeroParameter(7);
final Point3D<?, ?> point = new InnerComputationPoint3ai();
computeClosestPointTo(x1, y1, z1, radius1, x2, y2, z2, point);
return contains(x2, y2, z2, radius2, point.ix(), point.iy(), point.iz());
}
/** 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")
static boolean intersectsSphereRectangularPrism(int x1, int y1, int z1, int radius, int x2, int y2, int z2, int x3, int y3,
int z3) {
assert radius >= 0 : AssertMessages.positiveOrZeroParameter(3);
final Point3D<?, ?> point = new InnerComputationPoint3ai();
RectangularPrism3ai.computeClosestPoint(x2, y2, z2, x3, y3, z3, x1, y1, z1, point);
return contains(x1, y1, z1, radius, point.ix(), point.iy(), point.iz());
}
/** 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>
*/
@SuppressWarnings("checkstyle:parameternumber")
static boolean intersectsSphereSegment(int x1, int y1, int z1, int radius, int x2, int y2, int z2, int x3, int y3, int z3) {
assert radius >= 0 : AssertMessages.positiveOrZeroParameter(3);
final Point3D<?, ?> point = new InnerComputationPoint3ai();
Segment3ai.computeClosestPointToPoint(x2, y2, z1, x3, y3, z3, x1, y1, z1, point);
return contains(x1, y1, z1, radius, point.ix(), point.iy(), point.iz());
}
/** Replies the points of the sphere perimeters starting by the first octant.
*
* @param <P> the type of the points.
* @param <V> the type of the vectors.
* @param cx is the center of the radius.
* @param cy is the center of the radius.
* @param cz is the center of the radius.
* @param radius is the radius of the radius.
* @param firstOctantIndex is the index of the first octant to treat (value in [0;7].
* @param nbOctants is the number of octants to traverse (value in [0; 7 - firstOctantIndex].
* @param factory the factory to use for creating the points.
* @return the points on the perimeters.
*/
@Pure
@SuppressWarnings("checkstyle:magicnumber")
static <P extends Point3D<? super P, ? super V>, V extends Vector3D<? super V, ? super P>>
Iterator<P> getPointIterator(int cx, int cy, int cz, int radius, int firstOctantIndex, int nbOctants,
GeomFactory3ai<?, P, V, ?> factory) {
assert radius >= 0 : AssertMessages.positiveOrZeroParameter(3);
assert firstOctantIndex >= 0 && firstOctantIndex < 8
: AssertMessages.outsideRangeInclusiveParameter(firstOctantIndex, 0, 7);
int maxOctant;
maxOctant = Math.min(8, firstOctantIndex + nbOctants);
if (maxOctant > 8) {
maxOctant = 8;
}
return new SpherePerimeterIterator<>(
factory,
cx, cy, cz, radius,
firstOctantIndex, maxOctant, true);
}
/** Replies the points of the sphere perimeters starting by the first octant.
*
* @return the points on the perimeters.
*/
@Pure
@Override
@SuppressWarnings("checkstyle:magicnumber")
default Iterator<P> getPointIterator() {
return new SpherePerimeterIterator<>(getGeomFactory(), getX(), getY(), getZ(), getRadius(), 0, 8, true);
}
/** Replies the points of the sphere 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.
*/
@Pure
default Iterator<P> getPointIterator(int firstOctantIndex, int nbOctants) {
return getPointIterator(getX(), getY(), getZ(), getRadius(), firstOctantIndex, nbOctants, getGeomFactory());
}
@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();
}
@Override
default void clear() {
set(0, 0, 0, 0);
}
@Pure
@Override
default boolean isEmpty() {
return getRadius() <= 0;
}
@Override
default void set(IT shape) {
set(shape.getX(), shape.getY(), shape.getZ(), shape.getRadius());
}
/** Change the sphere.
*
* @param center the center of the sphere.
* @param radius the radius of the sphere.
*/
default void set(Point3D<?, ?> center, int radius) {
set(center.ix(), center.iy(), center.iz(), Math.abs(radius));
}
/** Change the sphere.
*
* @param x the x coordinate of the center.
* @param y the y coordinate of the center.
* @param z the z coordinate of the center.
* @param radius the radius of the center.
*/
void set(int x, int y, int z, int radius);
/** Change the sphere's center.
*
* @param center the center of the sphere.
*/
default void setCenter(Point3D<?, ?> center) {
set(center.ix(), center.iy(), center.iz(), getRadius());
}
/** Change the sphere's center.
*
* @param x x coordinate of the center of the sphere.
* @param y y coordinate of the center of the sphere.
* @param z z coordinate of the center of the sphere.
*/
default void setCenter(int x, int y, int z) {
set(x, y, z, getRadius());
}
/** Replies the center X.
*
* @return the center x.
*/
@Pure
int getX();
/** Change the center X.
*
* @param x the center x.
*/
@Pure
void setX(int x);
/** Replies the center y.
*
* @return the center y.
*/
@Pure
int getY();
/** Change the center Y.
*
* @param y the center y.
*/
@Pure
void setY(int y);
/** Replies the center z.
*
* @return the center z.
*/
@Pure
int getZ();
/** Change the center Z.
*
* @param z the center z.
*/
@Pure
void setZ(int z);
/** Replies the center.
*
* @return a copy of the center.
*/
@Pure
default P getCenter() {
return getGeomFactory().newPoint(getX(), getY(), getZ());
}
/** Replies the radius.
*
* @return the radius.
*/
@Pure
int getRadius();
/** Change the radius.
*
* @param radius the radius.
*/
@Pure
void setRadius(int radius);
@Pure
@Override
default void toBoundingBox(B box) {
assert box != null : AssertMessages.notNullParameter();
final int centerX = getX();
final int centerY = getY();
final int centerZ = getZ();
final int radius = getRadius();
box.setFromCorners(
centerX - radius,
centerY - radius,
centerZ - radius,
centerX + radius,
centerY + radius,
centerZ + radius);
}
@Pure
@Override
default double getDistanceSquared(Point3D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final P c = getClosestPointTo(pt);
return c.getDistanceSquared(pt);
}
@Pure
@Override
default double getDistanceL1(Point3D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final P c = getClosestPointTo(pt);
return c.getDistanceL1(pt);
}
@Pure
@Override
default double getDistanceLinf(Point3D<?, ?> pt) {
final P c = getClosestPointTo(pt);
return c.getDistanceLinf(pt);
}
@Pure
@Override
default P getClosestPointTo(Point3D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final P point = getGeomFactory().newPoint();
computeClosestPointTo(getX(), getY(), getZ(), getRadius(), pt.ix(), pt.iy(), pt.iz(), point);
return point;
}
@Override
default P getClosestPointTo(RectangularPrism3ai<?, ?, ?, ?, ?, ?> rectangle) {
throw new UnsupportedOperationException();
}
@Override
default P getClosestPointTo(Sphere3ai<?, ?, ?, ?, ?, ?> circle) {
throw new UnsupportedOperationException();
}
@Override
default P getClosestPointTo(Segment3ai<?, ?, ?, ?, ?, ?> segment) {
throw new UnsupportedOperationException();
}
@Override
default P getClosestPointTo(MultiShape3ai<?, ?, ?, ?, ?, ?, ?> multishape) {
throw new UnsupportedOperationException();
}
@Override
default P getClosestPointTo(Path3ai<?, ?, ?, ?, ?, ?> path) {
throw new UnsupportedOperationException();
}
@Pure
@Override
default P getFarthestPointTo(Point3D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final P point = getGeomFactory().newPoint();
computeFarthestPointTo(getX(), getY(), getZ(), getRadius(), pt.ix(), pt.iy(), pt.iz(), point);
return point;
}
@Pure
@Override
default boolean intersects(RectangularPrism3ai<?, ?, ?, ?, ?, ?> rectangularPrism) {
assert rectangularPrism != null : AssertMessages.notNullParameter();
return intersectsSphereRectangularPrism(
getX(), getY(), getZ(), getRadius(),
rectangularPrism.getMinX(), rectangularPrism.getMinY(), rectangularPrism.getMinZ(),
rectangularPrism.getMaxX(), rectangularPrism.getMaxY(), rectangularPrism.getMaxZ());
}
@Pure
@Override
default boolean intersects(Sphere3ai<?, ?, ?, ?, ?, ?> 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(Segment3ai<?, ?, ?, ?, ?, ?> 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(PathIterator3ai<?> iterator) {
assert iterator != null : AssertMessages.notNullParameter();
final int mask = iterator.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = Path3ai.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(MultiShape3ai<?, ?, ?, ?, ?, ?, ?> multishape) {
assert multishape != null : AssertMessages.notNullParameter();
return multishape.intersects(this);
}
@Override
default void translate(int dx, int dy, int dz) {
setCenter(getX() + dx, getY() + dy, getZ() + dz);
}
@Pure
@Override
default PathIterator3ai<IE> getPathIterator(Transform3D transform) {
if (transform == null || transform.isIdentity()) {
return new SpherePathIterator<>(this);
}
return new TransformedCirclePathIterator<>(this, transform);
}
/** Abstract iterator on the path elements of the sphere.
*
* @param <IE> is the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
// TODO integrate z coordinate
@SuppressWarnings("checkstyle:magicnumber")
abstract class AbstractCirclePathIterator<IE extends PathElement3ai> implements PathIterator3ai<IE> {
/**
* ArcIterator.btan(Math.PI/2).
*/
protected static final double CTRL_VAL = 0.5522847498307933f;
/**
* ctrlpts contains the control points for a set of 4 cubic
* bezier curves that approximate a sphere of radius 0.5
* centered at 0.5, 0.5.
*/
protected static final double PCV = 0.5f + CTRL_VAL * 0.5f;
/**
* ctrlpts contains the control points for a set of 4 cubic
* bezier curves that approximate a sphere of radius 0.5
* centered at 0.5, 0.5.
*/
protected static final double NCV = 0.5f - CTRL_VAL * 0.5f;
/**
* ctrlpts contains the control points for a set of 4 cubic
* bezier curves that approximate a sphere of radius 0.5
* centered at 0.5, 0.5.
*/
protected static final double[][] CTRL_PTS = {
{1.0, PCV, PCV, 1.0, 0.5, 1.0},
{NCV, 1.0, 0.0, PCV, 0.0, 0.5},
{0.0, NCV, NCV, 0.0, 0.5, 0.0},
{PCV, 0.0, 1.0, NCV, 1.0, 0.5},
};
/**
* The element factory.
*/
protected final Sphere3ai<?, ?, IE, ?, ?, ?> sphere;
/**
* @param sphere the sphere.
*/
public AbstractCirclePathIterator(Sphere3ai<?, ?, IE, ?, ?, ?> sphere) {
assert sphere != null : AssertMessages.notNullParameter();
this.sphere = sphere;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Pure
@Override
public PathWindingRule getWindingRule() {
return PathWindingRule.NON_ZERO;
}
@Pure
@Override
public boolean isPolyline() {
return false;
}
@Override
public GeomFactory3ai<IE, ?, ?, ?> getGeomFactory() {
return this.sphere.getGeomFactory();
}
@Override
public boolean isCurved() {
return true;
}
@Override
public boolean isMultiParts() {
return false;
}
@Override
public boolean isPolygon() {
return true;
}
}
/** Iterator on the path elements of the sphere.
*
* @param <IE> is the type of the path elements.
* @author $Author: sgalland$
* @author $Author: tpiotrow$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
@SuppressWarnings("checkstyle:magicnumber")
class SpherePathIterator<IE extends PathElement3ai> extends AbstractCirclePathIterator<IE> {
private int x;
private int y;
private int z;
private int radius;
private int index;
private int movex;
private int movey;
private int movez;
private int lastx;
private int lasty;
private int lastz;
/**
* @param sphere the sphere to iterate on.
*/
public SpherePathIterator(Sphere3ai<?, ?, IE, ?, ?, ?> sphere) {
super(sphere);
if (sphere.isEmpty()) {
this.index = 6;
} else {
this.radius = sphere.getRadius();
this.x = sphere.getX() - this.radius;
this.y = sphere.getY() - this.radius;
this.z = sphere.getZ() - this.radius;
}
}
@Override
public PathIterator3ai<IE> restartIterations() {
return new SpherePathIterator<>(this.sphere);
}
@Pure
@Override
public boolean hasNext() {
return this.index <= 5;
}
// TODO : check indexes
@Override
public IE next() {
if (this.index > 5) {
throw new NoSuchElementException();
}
final int idx = this.index;
++this.index;
if (idx == 0) {
final int dr = 2 * this.radius;
final double[] ctrls = CTRL_PTS[3];
this.movex = (int) (this.x + ctrls[6] * dr);
this.movey = (int) (this.y + ctrls[7] * dr);
this.movez = (int) (this.z + ctrls[8] * dr);
this.lastx = this.movex;
this.lasty = this.movey;
this.lastz = this.movez;
return getGeomFactory().newMovePathElement(
this.lastx, this.lasty, this.lastz);
} else if (idx < 5) {
final int dr = 2 * this.radius;
final double[] ctrls = CTRL_PTS[idx - 1];
final int ppx = this.lastx;
final int ppy = this.lasty;
final int ppz = this.lastz;
this.lastx = (int) (this.x + ctrls[6] * dr);
this.lasty = (int) (this.y + ctrls[7] * dr);
this.lastz = (int) (this.z + ctrls[8] * dr);
return getGeomFactory().newCurvePathElement(ppx, ppy, ppz, (int) (this.x + ctrls[0] * dr),
(int) (this.y + ctrls[1] * dr), (int) (this.z + ctrls[2] * dr), (int) (this.x + ctrls[3] * dr),
(int) (this.y + ctrls[4] * dr), (int) (this.z + ctrls[5] * dr), this.lastx, this.lasty, this.lastz);
}
final int ppx = this.lastx;
final int ppy = this.lasty;
final int ppz = this.lastz;
this.lastx = this.movex;
this.lasty = this.movey;
this.lastz = this.movez;
return getGeomFactory().newClosePathElement(
ppx, ppy, ppz,
this.lastx, this.lasty, this.lastz);
}
}
/** Iterator on the path elements of the sphere.
*
* @param <IE> is the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
@SuppressWarnings("checkstyle:magicnumber")
class TransformedCirclePathIterator<IE extends PathElement3ai> extends AbstractCirclePathIterator<IE> {
private final Transform3D transform;
private int x;
private int y;
private int z;
private int radius;
private int index;
private int movex;
private int movey;
private int movez;
private Point3D<?, ?> p1;
private Point3D<?, ?> p2;
private Point3D<?, ?> ptmp1;
private Point3D<?, ?> ptmp2;
/**
* @param sphere the sphere to iterate on.
* @param transform the transformation to apply.
*/
public TransformedCirclePathIterator(Sphere3ai<?, ?, IE, ?, ?, ?> sphere, Transform3D transform) {
super(sphere);
assert transform != null : AssertMessages.notNullParameter(1);
this.transform = transform;
if (sphere.isEmpty()) {
this.index = 6;
} else {
this.p1 = new InnerComputationPoint3ai();
this.p2 = new InnerComputationPoint3ai();
this.ptmp1 = new InnerComputationPoint3ai();
this.ptmp2 = new InnerComputationPoint3ai();
this.radius = sphere.getRadius();
this.x = sphere.getX() - this.radius;
this.y = sphere.getY() - this.radius;
this.z = sphere.getZ() - this.radius;
}
}
@Override
public PathIterator3ai<IE> restartIterations() {
return new TransformedCirclePathIterator<>(this.sphere, this.transform);
}
@Pure
@Override
public boolean hasNext() {
return this.index <= 5;
}
@Override
public IE next() {
if (this.index > 5) {
throw new NoSuchElementException();
}
final int idx = this.index;
++this.index;
if (idx == 0) {
final int dr = 2 * this.radius;
final double[] ctrls = CTRL_PTS[3];
this.movex = (int) (this.x + ctrls[6] * dr);
this.movey = (int) (this.y + ctrls[7] * dr);
this.movez = (int) (this.z + ctrls[8] * dr);
this.p2.set(this.movex, this.movey, this.movez);
this.transform.transform(this.p2);
return getGeomFactory().newMovePathElement(this.p2.ix(), this.p2.iy(), this.p2.iz());
} else if (idx < 5) {
final int dr = 2 * this.radius;
final double[] ctrls = CTRL_PTS[idx - 1];
this.p1.set(this.p2);
this.p2.set(this.x + ctrls[6] * dr, this.y + ctrls[7] * dr, this.z + ctrls[8] * dr);
this.transform.transform(this.p2);
this.ptmp1.set(this.x + ctrls[0] * dr, this.x + ctrls[1] * dr, this.y + ctrls[2] * dr);
this.transform.transform(this.ptmp1);
this.ptmp2.set(this.x + ctrls[3] * dr, this.y + ctrls[4] * dr, this.y + ctrls[5] * dr);
this.transform.transform(this.ptmp2);
return getGeomFactory().newCurvePathElement(this.p1.ix(), this.p1.iy(), this.p1.iz(), this.ptmp1.ix(),
this.ptmp1.iy(), this.ptmp1.iz(), this.ptmp2.ix(), this.ptmp2.iy(), this.ptmp2.iz(), this.p2.ix(),
this.p2.iy(), this.p2.iz());
}
this.p1.set(this.p2);
this.p2.set(this.movex, this.movey, this.movez);
this.transform.transform(this.p2);
return getGeomFactory().newClosePathElement(this.p1.ix(), this.p1.iy(), this.p1.iz(), this.p2.ix(), this.p2.iy(),
this.p2.iz());
}
}
/** Iterates on points on the perimeter of a sphere.
*
* <p>The rastrerization is based on a Bresenham algorithm.
*
* @param <P> the type of the points.
* @param <V> the type of the vectors.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
@SuppressWarnings("checkstyle:magicnumber")
class SpherePerimeterIterator<P extends Point3D<? super P, ? super V>,
V extends Vector3D<? super V, ? super P>> implements Iterator<P> {
private final GeomFactory3D<V, P> factory;
private final int cx;
private final int cy;
//TODO private final int cz;
private final int cr;
private final boolean skip;
private final int maxOctant;
private int currentOctant;
private int x;
private int y;
//TODO private int z;
private int dval;
private P next;
private final Set<P> junctionPoint = new TreeSet<>(new Tuple3iComparator());
/** Construct the iterator from the initialOctant (inclusive) to the lastOctant (exclusive).
*
* @param factory the point factory.
* @param centerX the x coordinate of the center of the sphere.
* @param centerY the y coordinate of the center of the sphere.
* @param centerZ the y coordinate of the center of the sphere.
* @param radius the radius of the sphere.
* @param initialOctant the octant from which the iteration must start.
* @param lastOctant the first octant that must not be iterated on.
* @param skip indicates if the first point on an octant must be skip, because it is already replied when treating the
* previous octant.
*/
public SpherePerimeterIterator(GeomFactory3D<V, P> factory,
int centerX, int centerY, int centerZ, int radius, int initialOctant, int lastOctant, boolean skip) {
assert factory != null : AssertMessages.notNullParameter(0);
assert radius >= 0 : AssertMessages.positiveOrZeroParameter(4);
assert initialOctant >= 0 && initialOctant < 8
: AssertMessages.outsideRangeInclusiveParameter(initialOctant, 0, 7);
assert lastOctant > initialOctant && lastOctant <= 8
: AssertMessages.outsideRangeInclusiveParameter(lastOctant, initialOctant + 1, 8);
this.factory = factory;
this.cx = centerX;
this.cy = centerY;
//TODO this.cz = centerZ;
this.cr = radius;
this.skip = skip;
this.maxOctant = lastOctant;
this.currentOctant = initialOctant;
reset();
searchNext();
}
private void reset() {
this.x = 0;
this.y = this.cr;
this.dval = 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.dval <= 0) {
this.dval += 4 * this.x + 6;
} else {
this.dval += 4 * (this.x - this.y) + 10;
--this.y;
}
++this.x;
}
}
@Pure
@Override
public boolean hasNext() {
return this.next != null;
}
// TODO : integrate z coordinate
@SuppressWarnings("checkstyle:cyclomaticcomplexity")
private void searchNext() {
if (this.currentOctant >= this.maxOctant) {
this.next = null;
} else {
this.next = this.factory.newPoint();
while (true) {
switch (this.currentOctant) {
case 0:
this.next.set(this.cx + this.x, this.cy + this.y, 0);
break;
case 1:
this.next.set(this.cx + this.y, this.cy + this.x, 0);
break;
case 2:
this.next.set(this.cx + this.x, this.cy - this.y, 0);
break;
case 3:
this.next.set(this.cx + this.y, this.cy - this.x, 0);
break;
case 4:
this.next.set(this.cx - this.x, this.cy - this.y, 0);
break;
case 5:
this.next.set(this.cx - this.y, this.cy - this.x, 0);
break;
case 6:
this.next.set(this.cx - this.x, this.cy + this.y, 0);
break;
case 7:
this.next.set(this.cx - this.y, this.cy + this.x, 0);
break;
default:
throw new NoSuchElementException();
}
if (this.dval <= 0) {
this.dval += 4 * this.x + 6;
} else {
this.dval += 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) {
final P point = this.factory.newPoint();
point.set(this.next.ix(), this.next.iy(), this.next.iz());
this.junctionPoint.add(point);
}
// Goto next.
++this.currentOctant;
reset();
if (this.currentOctant >= this.maxOctant) {
if (cont) {
this.next = null;
}
cont = false;
}
if (!cont) {
return;
}
} else {
return;
}
}
}
}
@Override
public P next() {
final P pixel = this.next;
if (pixel == null) {
throw new NoSuchElementException();
}
searchNext();
return pixel;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}